Merge branch 'kvm-arm64/asahi-base-6.6' into kvm-arm64/asahi-base-6.7
Signed-off-by: Marc Zyngier <maz@kernel.org>
diff --git a/Documentation/core-api/printk-formats.rst b/Documentation/core-api/printk-formats.rst
index 4451ef5..c726a84 100644
--- a/Documentation/core-api/printk-formats.rst
+++ b/Documentation/core-api/printk-formats.rst
@@ -632,6 +632,38 @@
%p4cc Y10 little-endian (0x20303159)
%p4cc NV12 big-endian (0xb231564e)
+Generic FourCC code
+-------------------
+
+::
+ %p4c[hnbl] gP00 (0x67503030)
+
+Print a generic FourCC code, as both ASCII characters and its numerical
+value as hexadecimal.
+
+The additional ``h``, ``r``, ``b``, and ``l`` specifiers are used to specify
+host, reversed, big or little endian order data respectively. Host endian
+order means the data is interpreted as a 32-bit integer and the most
+significant byte is printed first; that is, the character code as printed
+matches the byte order stored in memory on big-endian systems, and is reversed
+on little-endian systems.
+
+Passed by reference.
+
+Examples for a little-endian machine, given &(u32)0x67503030::
+
+ %p4ch gP00 (0x67503030)
+ %p4cl gP00 (0x67503030)
+ %p4cb 00Pg (0x30305067)
+ %p4cr 00Pg (0x30305067)
+
+Examples for a big-endian machine, given &(u32)0x67503030::
+
+ %p4ch gP00 (0x67503030)
+ %p4cl 00Pg (0x30305067)
+ %p4cb gP00 (0x67503030)
+ %p4cr 00Pg (0x30305067)
+
Rust
----
diff --git a/Documentation/devicetree/bindings/pci/apple,pcie.yaml b/Documentation/devicetree/bindings/pci/apple,pcie.yaml
index 215ff9a..8936f8a 100644
--- a/Documentation/devicetree/bindings/pci/apple,pcie.yaml
+++ b/Documentation/devicetree/bindings/pci/apple,pcie.yaml
@@ -72,6 +72,27 @@
power-domains:
maxItems: 1
+patternProperties:
+ "^pci@":
+ $ref: /schemas/pci/pci-bus.yaml#
+ type: object
+ description: A single PCI root port
+
+ properties:
+ reg:
+ maxItems: 1
+
+ pwren-gpios:
+ description: Optional GPIO to power on the device
+ maxItems: 1
+
+ required:
+ - reset-gpios
+ - interrupt-controller
+ - "#interrupt-cells"
+ - interrupt-map-mask
+ - interrupt-map
+
required:
- compatible
- reg
@@ -142,7 +163,7 @@
pinctrl-0 = <&pcie_pins>;
pinctrl-names = "default";
- pci@0,0 {
+ port00: pci@0,0 {
device_type = "pci";
reg = <0x0 0x0 0x0 0x0 0x0>;
reset-gpios = <&pinctrl_ap 152 0>;
@@ -150,9 +171,17 @@
#address-cells = <3>;
#size-cells = <2>;
ranges;
+
+ interrupt-controller;
+ #interrupt-cells = <1>;
+ interrupt-map-mask = <0 0 0 7>;
+ interrupt-map = <0 0 0 1 &port00 0 0 0 0>,
+ <0 0 0 2 &port00 0 0 0 1>,
+ <0 0 0 3 &port00 0 0 0 2>,
+ <0 0 0 4 &port00 0 0 0 3>;
};
- pci@1,0 {
+ port01: pci@1,0 {
device_type = "pci";
reg = <0x800 0x0 0x0 0x0 0x0>;
reset-gpios = <&pinctrl_ap 153 0>;
@@ -160,9 +189,17 @@
#address-cells = <3>;
#size-cells = <2>;
ranges;
+
+ interrupt-controller;
+ #interrupt-cells = <1>;
+ interrupt-map-mask = <0 0 0 7>;
+ interrupt-map = <0 0 0 1 &port01 0 0 0 0>,
+ <0 0 0 2 &port01 0 0 0 1>,
+ <0 0 0 3 &port01 0 0 0 2>,
+ <0 0 0 4 &port01 0 0 0 3>;
};
- pci@2,0 {
+ port02: pci@2,0 {
device_type = "pci";
reg = <0x1000 0x0 0x0 0x0 0x0>;
reset-gpios = <&pinctrl_ap 33 0>;
@@ -170,6 +207,14 @@
#address-cells = <3>;
#size-cells = <2>;
ranges;
+
+ interrupt-controller;
+ #interrupt-cells = <1>;
+ interrupt-map-mask = <0 0 0 7>;
+ interrupt-map = <0 0 0 1 &port02 0 0 0 0>,
+ <0 0 0 2 &port02 0 0 0 1>,
+ <0 0 0 3 &port02 0 0 0 2>,
+ <0 0 0 4 &port02 0 0 0 3>;
};
};
};
diff --git a/Documentation/devicetree/bindings/sound/apple,macaudio.yaml b/Documentation/devicetree/bindings/sound/apple,macaudio.yaml
new file mode 100644
index 0000000..8fe22de
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/apple,macaudio.yaml
@@ -0,0 +1,162 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/apple,macaudio.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Apple Silicon Macs integrated sound peripherals
+
+description:
+ This binding represents the overall machine-level integration of sound
+ peripherals on 'Apple Silicon' machines by Apple.
+
+maintainers:
+ - Martin Povišer <povik+lin@cutebit.org>
+
+properties:
+ compatible:
+ items:
+ - enum:
+ - apple,j274-macaudio
+ - apple,j293-macaudio
+ - apple,j314-macaudio
+ - const: apple,macaudio
+
+ "#address-cells":
+ const: 1
+
+ "#size-cells":
+ const: 0
+
+ model:
+ description:
+ Model name for presentation to users
+ $ref: /schemas/types.yaml#/definitions/string
+
+patternProperties:
+ "^dai-link(@[0-9a-f]+)?$":
+ description: |
+ Node for each sound peripheral such as the speaker array, headphones jack,
+ or microphone.
+ type: object
+
+ additionalProperties: false
+
+ properties:
+ reg:
+ maxItems: 1
+
+ link-name:
+ description: |
+ Name for the peripheral, expecting 'Speaker' or 'Speakers' if this is
+ the speaker array.
+ $ref: /schemas/types.yaml#/definitions/string
+
+ cpu:
+ type: object
+
+ properties:
+ sound-dai:
+ description: |
+ DAI list with CPU-side I2S ports involved in this peripheral.
+ minItems: 1
+ maxItems: 2
+
+ required:
+ - sound-dai
+
+ codec:
+ type: object
+
+ properties:
+ sound-dai:
+ minItems: 1
+ maxItems: 8
+ description: |
+ DAI list with the CODEC-side DAIs connected to the above CPU-side
+ DAIs and involved in this sound peripheral.
+
+ The list is in left/right order if applicable. If there are more
+ than one CPU-side DAIs (there can be two), the CODECs must be
+ listed first those connected to the first CPU, then those
+ connected to the second.
+
+ In addition, on some machines with many speaker codecs, the CODECs
+ are listed in this fixed order:
+
+ J293: Left Front, Left Rear, Right Front, Right Rear
+ J314: Left Woofer 1, Left Tweeter, Left Woofer 2,
+ Right Woofer 1, Right Tweeter, Right Woofer 2
+
+ required:
+ - sound-dai
+
+ required:
+ - reg
+ - cpu
+ - codec
+
+required:
+ - compatible
+ - model
+
+additionalProperties: false
+
+examples:
+ - |
+ mca: mca@9b600000 {
+ compatible = "apple,t6000-mca", "apple,mca";
+ reg = <0x9b600000 0x10000>,
+ <0x9b500000 0x20000>;
+
+ clocks = <&nco 0>, <&nco 1>, <&nco 2>, <&nco 3>;
+ power-domains = <&ps_audio_p>, <&ps_mca0>, <&ps_mca1>,
+ <&ps_mca2>, <&ps_mca3>;
+ dmas = <&admac 0>, <&admac 1>, <&admac 2>, <&admac 3>,
+ <&admac 4>, <&admac 5>, <&admac 6>, <&admac 7>,
+ <&admac 8>, <&admac 9>, <&admac 10>, <&admac 11>,
+ <&admac 12>, <&admac 13>, <&admac 14>, <&admac 15>;
+ dma-names = "tx0a", "rx0a", "tx0b", "rx0b",
+ "tx1a", "rx1a", "tx1b", "rx1b",
+ "tx2a", "rx2a", "tx2b", "rx2b",
+ "tx3a", "rx3a", "tx3b", "rx3b";
+
+ #sound-dai-cells = <1>;
+ };
+
+ sound {
+ compatible = "apple,j314-macaudio", "apple,macaudio";
+ model = "MacBook Pro J314 integrated audio";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ dai-link@0 {
+ reg = <0>;
+ link-name = "Speakers";
+
+ cpu {
+ sound-dai = <&mca 0>, <&mca 1>;
+ };
+ codec {
+ sound-dai = <&speaker_left_woof1>,
+ <&speaker_left_tweet>,
+ <&speaker_left_woof2>,
+ <&speaker_right_woof1>,
+ <&speaker_right_tweet>,
+ <&speaker_right_woof2>;
+ };
+ };
+
+ dai-link@1 {
+ reg = <1>;
+ link-name = "Headphones Jack";
+
+ cpu {
+ sound-dai = <&mca 2>;
+ };
+ codec {
+ sound-dai = <&jack_codec>;
+ };
+ };
+ };
diff --git a/Documentation/devicetree/bindings/spi/apple,spi.yaml b/Documentation/devicetree/bindings/spi/apple,spi.yaml
new file mode 100644
index 0000000..bcbdc89
--- /dev/null
+++ b/Documentation/devicetree/bindings/spi/apple,spi.yaml
@@ -0,0 +1,63 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/spi/apple,spi.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Apple ARM SoC SPI controller
+
+allOf:
+ - $ref: "spi-controller.yaml#"
+
+maintainers:
+ - Hector Martin <marcan@marcan.st>
+
+properties:
+ compatible:
+ items:
+ - enum:
+ - apple,t8103-spi
+ - apple,t6000-spi
+ - const: apple,spi
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ power-domains:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - interrupts
+ - '#address-cells'
+ - '#size-cells'
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/apple-aic.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ soc {
+ #address-cells = <2>;
+ #size-cells = <2>;
+
+ spi: spi@39b104000 {
+ compatible = "apple,t6000-spi", "apple,spi";
+ reg = <0x3 0x9b104000 0x0 0x4000>;
+ interrupt-parent = <&aic>;
+ interrupts = <AIC_IRQ 0 1107 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ clocks = <&clk>;
+ };
+ };
diff --git a/Documentation/devicetree/bindings/usb/apple,dwc3.yaml b/Documentation/devicetree/bindings/usb/apple,dwc3.yaml
new file mode 100644
index 0000000..fb3b348
--- /dev/null
+++ b/Documentation/devicetree/bindings/usb/apple,dwc3.yaml
@@ -0,0 +1,64 @@
+# SPDX-License-Identifier: GPL-2.0
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/usb/apple,dwc3.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Apple Silicon DWC3 USB controller
+
+maintainers:
+ - Sven Peter <sven@svenpeter.dev>
+
+description:
+ On Apple Silicon SoCs such as the M1 each Type-C port has a corresponding
+ USB controller based on the Synopsys DesignWare USB3 controller.
+
+ The common content of this binding is defined in snps,dwc3.yaml.
+
+allOf:
+ - $ref: snps,dwc3.yaml#
+
+select:
+ properties:
+ compatible:
+ contains:
+ const: apple,dwc3
+ required:
+ - compatible
+
+properties:
+ compatible:
+ items:
+ - enum:
+ - apple,t8103-dwc3
+ - apple,t6000-dwc3
+ - const: apple,dwc3
+ - const: snps,dwc3
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+unevaluatedProperties: false
+
+required:
+ - compatible
+ - reg
+ - interrupts
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/apple-aic.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ usb@82280000 {
+ compatible = "apple,t8103-dwc3", "apple,dwc3", "snps,dwc3";
+ reg = <0x82280000 0x10000>;
+ interrupts = <AIC_IRQ 777 IRQ_TYPE_LEVEL_HIGH>;
+
+ dr_mode = "otg";
+ usb-role-switch;
+ role-switch-default-mode = "host";
+ };
diff --git a/drivers/base/core.c b/drivers/base/core.c
index 67ba592..75d9de1 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -191,6 +191,33 @@ void fw_devlink_purge_absent_suppliers(struct fwnode_handle *fwnode)
EXPORT_SYMBOL_GPL(fw_devlink_purge_absent_suppliers);
/**
+ * fw_devlink_count_absent_consumers - Return how many consumers have
+ * either not been created yet, or do not yet have a driver attached.
+ * @fwnode: fwnode of the supplier
+ */
+int fw_devlink_count_absent_consumers(struct fwnode_handle *fwnode)
+{
+ struct fwnode_link *link, *tmp;
+ struct device_link *dlink, *dtmp;
+ struct device *sup_dev = get_dev_from_fwnode(fwnode);
+ int count = 0;
+
+ list_for_each_entry_safe(link, tmp, &fwnode->consumers, s_hook)
+ count++;
+
+ if (!sup_dev)
+ return count;
+
+ list_for_each_entry_safe(dlink, dtmp, &sup_dev->links.consumers, s_node)
+ if (dlink->consumer->links.status != DL_DEV_DRIVER_BOUND)
+ count++;
+
+ return count;
+}
+EXPORT_SYMBOL_GPL(fw_devlink_count_absent_consumers);
+
+
+/**
* __fwnode_links_move_consumers - Move consumer from @from to @to fwnode_handle
* @from: move consumers away from this fwnode
* @to: move consumers to this fwnode
diff --git a/drivers/base/firmware_loader/main.c b/drivers/base/firmware_loader/main.c
index ea28102..e96b3d6 100644
--- a/drivers/base/firmware_loader/main.c
+++ b/drivers/base/firmware_loader/main.c
@@ -471,6 +471,8 @@ static int fw_decompress_xz(struct device *dev, struct fw_priv *fw_priv,
static char fw_path_para[256];
static const char * const fw_path[] = {
fw_path_para,
+ "/lib/firmware/vendor/" UTS_RELEASE,
+ "/lib/firmware/vendor",
"/lib/firmware/updates/" UTS_RELEASE,
"/lib/firmware/updates",
"/lib/firmware/" UTS_RELEASE,
diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c
index da1777e..b89cd9a 100644
--- a/drivers/base/power/domain.c
+++ b/drivers/base/power/domain.c
@@ -7,6 +7,7 @@
#define pr_fmt(fmt) "PM: " fmt
#include <linux/delay.h>
+#include <linux/fwnode.h>
#include <linux/kernel.h>
#include <linux/io.h>
#include <linux/platform_device.h>
@@ -131,6 +132,7 @@ static const struct genpd_lock_ops genpd_spin_ops = {
#define genpd_is_cpu_domain(genpd) (genpd->flags & GENPD_FLAG_CPU_DOMAIN)
#define genpd_is_rpm_always_on(genpd) (genpd->flags & GENPD_FLAG_RPM_ALWAYS_ON)
#define genpd_is_opp_table_fw(genpd) (genpd->flags & GENPD_FLAG_OPP_TABLE_FW)
+#define genpd_is_defer_off(genpd) (genpd->flags & GENPD_FLAG_DEFER_OFF)
static inline bool irq_safe_dev_in_sleep_domain(struct device *dev,
const struct generic_pm_domain *genpd)
@@ -664,6 +666,27 @@ static void genpd_queue_power_off_work(struct generic_pm_domain *genpd)
}
/**
+ * genpd_must_defer - Check whether the genpd cannot be safely powered off.
+ * @genpd: PM domain about to be powered down.
+ * @one_dev_probing: True if we are being called from RPM callbacks on a device that
+ * is probing, to allow poweroff if that device is the sole remaining consumer probing.
+ *
+ * Returns true if the @genpd has the GENPD_FLAG_DEFER_OFF flag and there
+ * are any consumer devices which either do not exist yet (only represented
+ * by fwlinks) or whose drivers have not probed yet.
+ */
+static bool genpd_must_defer(struct generic_pm_domain *genpd, bool one_dev_probing)
+{
+ if (genpd_is_defer_off(genpd) && genpd->has_provider) {
+ int absent = fw_devlink_count_absent_consumers(genpd->provider);
+
+ if (absent > (one_dev_probing ? 1 : 0))
+ return true;
+ }
+ return false;
+}
+
+/**
* genpd_power_off - Remove power from a given PM domain.
* @genpd: PM domain to power down.
* @one_dev_on: If invoked from genpd's ->runtime_suspend|resume() callback, the
@@ -676,7 +699,7 @@ static void genpd_queue_power_off_work(struct generic_pm_domain *genpd)
* have been powered down, remove power from @genpd.
*/
static int genpd_power_off(struct generic_pm_domain *genpd, bool one_dev_on,
- unsigned int depth)
+ bool one_dev_probing, unsigned int depth)
{
struct pm_domain_data *pdd;
struct gpd_link *link;
@@ -726,6 +749,14 @@ static int genpd_power_off(struct generic_pm_domain *genpd, bool one_dev_on,
if (not_suspended > 1 || (not_suspended == 1 && !one_dev_on))
return -EBUSY;
+ /*
+ * Do not allow PM domain to be powered off if it is marked
+ * as GENPD_FLAG_DEFER_OFF and there are consumer devices
+ * which have not probed yet.
+ */
+ if (genpd_must_defer(genpd, one_dev_probing))
+ return -EBUSY;
+
if (genpd->gov && genpd->gov->power_down_ok) {
if (!genpd->gov->power_down_ok(&genpd->domain))
return -EAGAIN;
@@ -752,7 +783,7 @@ static int genpd_power_off(struct generic_pm_domain *genpd, bool one_dev_on,
list_for_each_entry(link, &genpd->child_links, child_node) {
genpd_sd_counter_dec(link->parent);
genpd_lock_nested(link->parent, depth + 1);
- genpd_power_off(link->parent, false, depth + 1);
+ genpd_power_off(link->parent, false, false, depth + 1);
genpd_unlock(link->parent);
}
@@ -810,7 +841,7 @@ static int genpd_power_on(struct generic_pm_domain *genpd, unsigned int depth)
child_node) {
genpd_sd_counter_dec(link->parent);
genpd_lock_nested(link->parent, depth + 1);
- genpd_power_off(link->parent, false, depth + 1);
+ genpd_power_off(link->parent, false, false, depth + 1);
genpd_unlock(link->parent);
}
@@ -877,7 +908,7 @@ static void genpd_power_off_work_fn(struct work_struct *work)
genpd = container_of(work, struct generic_pm_domain, power_off_work);
genpd_lock(genpd);
- genpd_power_off(genpd, false, 0);
+ genpd_power_off(genpd, false, false, 0);
genpd_unlock(genpd);
}
@@ -942,6 +973,7 @@ static int genpd_runtime_suspend(struct device *dev)
struct generic_pm_domain_data *gpd_data = dev_gpd_data(dev);
struct gpd_timing_data *td = gpd_data->td;
bool runtime_pm = pm_runtime_enabled(dev);
+ bool probing = dev->links.status != DL_DEV_DRIVER_BOUND;
ktime_t time_start = 0;
s64 elapsed_ns;
int ret;
@@ -996,7 +1028,7 @@ static int genpd_runtime_suspend(struct device *dev)
return 0;
genpd_lock(genpd);
- genpd_power_off(genpd, true, 0);
+ genpd_power_off(genpd, true, probing, 0);
gpd_data->rpm_pstate = genpd_drop_performance_state(dev);
genpd_unlock(genpd);
@@ -1017,6 +1049,7 @@ static int genpd_runtime_resume(struct device *dev)
struct generic_pm_domain_data *gpd_data = dev_gpd_data(dev);
struct gpd_timing_data *td = gpd_data->td;
bool timed = td && pm_runtime_enabled(dev);
+ bool probing = dev->links.status != DL_DEV_DRIVER_BOUND;
ktime_t time_start = 0;
s64 elapsed_ns;
int ret;
@@ -1074,7 +1107,7 @@ static int genpd_runtime_resume(struct device *dev)
err_poweroff:
if (!pm_runtime_is_irq_safe(dev) || genpd_is_irq_safe(genpd)) {
genpd_lock(genpd);
- genpd_power_off(genpd, true, 0);
+ genpd_power_off(genpd, true, probing, 0);
gpd_data->rpm_pstate = genpd_drop_performance_state(dev);
genpd_unlock(genpd);
}
@@ -1140,6 +1173,9 @@ static void genpd_sync_power_off(struct generic_pm_domain *genpd, bool use_lock,
|| atomic_read(&genpd->sd_count) > 0)
return;
+ if (genpd_must_defer(genpd, false))
+ return;
+
/* Check that the children are in their deepest (powered-off) state. */
list_for_each_entry(link, &genpd->parent_links, parent_node) {
struct generic_pm_domain *child = link->child;
@@ -2106,6 +2142,12 @@ int pm_genpd_init(struct generic_pm_domain *genpd,
return -EINVAL;
}
+ /* Deferred-off power domains should be powered on at initialization. */
+ if (genpd_is_defer_off(genpd) && !genpd_status_on(genpd)) {
+ pr_warn("deferred-off PM domain %s is not on at init\n", genpd->name);
+ genpd->flags &= ~GENPD_FLAG_DEFER_OFF;
+ }
+
/* Multiple states but no governor doesn't make sense. */
if (!gov && genpd->state_count > 1)
pr_warn("%s: no governor for states\n", genpd->name);
diff --git a/drivers/cpufreq/apple-soc-cpufreq.c b/drivers/cpufreq/apple-soc-cpufreq.c
index 021f423..51a7132 100644
--- a/drivers/cpufreq/apple-soc-cpufreq.c
+++ b/drivers/cpufreq/apple-soc-cpufreq.c
@@ -25,7 +25,7 @@
#define APPLE_DVFS_CMD 0x20
#define APPLE_DVFS_CMD_BUSY BIT(31)
#define APPLE_DVFS_CMD_SET BIT(25)
-#define APPLE_DVFS_CMD_PS2 GENMASK(16, 12)
+#define APPLE_DVFS_CMD_PS2 GENMASK(15, 12)
#define APPLE_DVFS_CMD_PS1 GENMASK(4, 0)
/* Same timebase as CPU counter (24MHz) */
@@ -55,6 +55,7 @@
#define APPLE_DVFS_TRANSITION_TIMEOUT 100
struct apple_soc_cpufreq_info {
+ bool has_ps2;
u64 max_pstate;
u64 cur_pstate_mask;
u64 cur_pstate_shift;
@@ -69,18 +70,21 @@ struct apple_cpu_priv {
static struct cpufreq_driver apple_soc_cpufreq_driver;
static const struct apple_soc_cpufreq_info soc_t8103_info = {
+ .has_ps2 = true,
.max_pstate = 15,
.cur_pstate_mask = APPLE_DVFS_STATUS_CUR_PS_T8103,
.cur_pstate_shift = APPLE_DVFS_STATUS_CUR_PS_SHIFT_T8103,
};
static const struct apple_soc_cpufreq_info soc_t8112_info = {
+ .has_ps2 = false,
.max_pstate = 31,
.cur_pstate_mask = APPLE_DVFS_STATUS_CUR_PS_T8112,
.cur_pstate_shift = APPLE_DVFS_STATUS_CUR_PS_SHIFT_T8112,
};
static const struct apple_soc_cpufreq_info soc_default_info = {
+ .has_ps2 = false,
.max_pstate = 15,
.cur_pstate_mask = 0, /* fallback */
};
@@ -148,9 +152,12 @@ static int apple_soc_cpufreq_set_target(struct cpufreq_policy *policy,
return -EIO;
}
- reg &= ~(APPLE_DVFS_CMD_PS1 | APPLE_DVFS_CMD_PS2);
+ reg &= ~APPLE_DVFS_CMD_PS1;
reg |= FIELD_PREP(APPLE_DVFS_CMD_PS1, pstate);
- reg |= FIELD_PREP(APPLE_DVFS_CMD_PS2, pstate);
+ if (priv->info->has_ps2) {
+ reg &= ~APPLE_DVFS_CMD_PS2;
+ reg |= FIELD_PREP(APPLE_DVFS_CMD_PS2, pstate);
+ }
reg |= APPLE_DVFS_CMD_SET;
writeq_relaxed(reg, priv->reg_base + APPLE_DVFS_CMD);
diff --git a/drivers/cpuidle/Kconfig.arm b/drivers/cpuidle/Kconfig.arm
index a1ee475..c6870f0 100644
--- a/drivers/cpuidle/Kconfig.arm
+++ b/drivers/cpuidle/Kconfig.arm
@@ -130,3 +130,11 @@
The Subsystem Power Manager (SPM) controls low power modes for the
CPU and L2 cores. It interface with various system drivers to put
the cores in low power modes.
+
+config ARM_APPLE_CPUIDLE
+ bool "Apple SoC CPU idle driver"
+ depends on ARM64
+ default ARCH_APPLE
+ select CPU_IDLE_MULTIPLE_DRIVERS
+ help
+ Select this to enable cpuidle on Apple SoCs.
diff --git a/drivers/cpuidle/Makefile b/drivers/cpuidle/Makefile
index d103342..972b49a 100644
--- a/drivers/cpuidle/Makefile
+++ b/drivers/cpuidle/Makefile
@@ -26,6 +26,7 @@
obj-$(CONFIG_ARM_PSCI_CPUIDLE_DOMAIN) += cpuidle-psci-domain.o
obj-$(CONFIG_ARM_TEGRA_CPUIDLE) += cpuidle-tegra.o
obj-$(CONFIG_ARM_QCOM_SPM_CPUIDLE) += cpuidle-qcom-spm.o
+obj-$(CONFIG_ARM_APPLE_CPUIDLE) += cpuidle-apple.o
###############################################################################
# MIPS drivers
diff --git a/drivers/cpuidle/cpuidle-apple.c b/drivers/cpuidle/cpuidle-apple.c
new file mode 100644
index 0000000..cd1c708
--- /dev/null
+++ b/drivers/cpuidle/cpuidle-apple.c
@@ -0,0 +1,146 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Copyright The Asahi Linux Contributors
+ *
+ * CPU idle support for Apple SoCs
+ */
+
+#include <linux/init.h>
+#include <linux/cpuidle.h>
+#include <linux/cpu_pm.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <asm/cpuidle.h>
+
+enum idle_state {
+ STATE_WFI,
+ STATE_PWRDOWN,
+ STATE_COUNT
+};
+
+asm(
+ ".type apple_cpu_deep_wfi, @function\n"
+ "apple_cpu_deep_wfi:\n"
+ "str x30, [sp, #-16]!\n"
+ "stp x28, x29, [sp, #-16]!\n"
+ "stp x26, x27, [sp, #-16]!\n"
+ "stp x24, x25, [sp, #-16]!\n"
+ "stp x22, x23, [sp, #-16]!\n"
+ "stp x20, x21, [sp, #-16]!\n"
+ "stp x18, x19, [sp, #-16]!\n"
+
+ "mrs x0, s3_5_c15_c5_0\n"
+ "orr x0, x0, #(3L << 24)\n"
+ "msr s3_5_c15_c5_0, x0\n"
+
+ "1:\n"
+ "dsb sy\n"
+ "wfi\n"
+
+ "mrs x0, ISR_EL1\n"
+ "cbz x0, 1b\n"
+
+ "mrs x0, s3_5_c15_c5_0\n"
+ "bic x0, x0, #(1L << 24)\n"
+ "msr s3_5_c15_c5_0, x0\n"
+
+ "ldp x18, x19, [sp], #16\n"
+ "ldp x20, x21, [sp], #16\n"
+ "ldp x22, x23, [sp], #16\n"
+ "ldp x24, x25, [sp], #16\n"
+ "ldp x26, x27, [sp], #16\n"
+ "ldp x28, x29, [sp], #16\n"
+ "ldr x30, [sp], #16\n"
+
+ "ret\n"
+);
+
+void apple_cpu_deep_wfi(void);
+
+static __cpuidle int apple_enter_idle(struct cpuidle_device *dev, struct cpuidle_driver *drv, int index)
+{
+ /*
+ * Deep WFI will clobber FP state, among other things.
+ * The CPU PM notifier will take care of saving that and anything else
+ * that needs to be notified of the CPU powering down.
+ */
+ if (cpu_pm_enter())
+ return -1;
+
+ switch(index) {
+ case STATE_WFI:
+ cpu_do_idle();
+ break;
+ case STATE_PWRDOWN:
+ apple_cpu_deep_wfi();
+ break;
+ default:
+ WARN_ON(1);
+ break;
+ }
+
+ cpu_pm_exit();
+
+ return index;
+}
+
+static struct cpuidle_driver apple_idle_driver = {
+ .name = "apple_idle",
+ .owner = THIS_MODULE,
+ .states = {
+ [STATE_WFI] = {
+ .enter = apple_enter_idle,
+ .enter_s2idle = apple_enter_idle,
+ .exit_latency = 1,
+ .target_residency = 1,
+ .power_usage = UINT_MAX,
+ .name = "WFI",
+ .desc = "CPU clock-gated",
+ },
+ [STATE_PWRDOWN] = {
+ .enter = apple_enter_idle,
+ .enter_s2idle = apple_enter_idle,
+ .exit_latency = 10,
+ .target_residency = 10000,
+ .power_usage = 0,
+ .name = "CPU PD",
+ .desc = "CPU/cluster powered down",
+ },
+ },
+ .safe_state_index = STATE_WFI,
+ .state_count = STATE_COUNT,
+};
+
+static int apple_cpuidle_probe(struct platform_device *pdev)
+{
+ return cpuidle_register(&apple_idle_driver, NULL);
+}
+
+static struct platform_driver apple_cpuidle_driver = {
+ .driver = {
+ .name = "cpuidle-apple",
+ },
+ .probe = apple_cpuidle_probe,
+};
+
+static int __init apple_cpuidle_init(void)
+{
+ struct platform_device *pdev;
+ int ret;
+
+ ret = platform_driver_register(&apple_cpuidle_driver);
+ if (ret)
+ return ret;
+
+ if (!of_machine_is_compatible("apple,arm-platform"))
+ return 0;
+
+ pdev = platform_device_register_simple("cpuidle-apple", -1, NULL, 0);
+ if (IS_ERR(pdev)) {
+ platform_driver_unregister(&apple_cpuidle_driver);
+ return PTR_ERR(pdev);
+ }
+
+ return 0;
+}
+device_initcall(apple_cpuidle_init);
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index b3a133e..b1c6039 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -1350,6 +1350,17 @@
This driver can also be built as a module. If so, the module will be
called gpio-lp87565.
+config GPIO_MACSMC
+ tristate "Apple Mac SMC GPIO"
+ depends on APPLE_SMC
+ default ARCH_APPLE
+ help
+ Support for GPIOs controlled by the SMC microcontroller on Apple Mac
+ systems.
+
+ This driver can also be built as a module. If so, the module will be
+ called gpio-macsmc.
+
config GPIO_MADERA
tristate "Cirrus Logic Madera class codecs"
depends on PINCTRL_MADERA
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index eb73b5d..ca78337 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -90,6 +90,7 @@
obj-$(CONFIG_GPIO_LP87565) += gpio-lp87565.o
obj-$(CONFIG_GPIO_LPC18XX) += gpio-lpc18xx.o
obj-$(CONFIG_GPIO_LPC32XX) += gpio-lpc32xx.o
+obj-$(CONFIG_GPIO_MACSMC) += gpio-macsmc.o
obj-$(CONFIG_GPIO_MADERA) += gpio-madera.o
obj-$(CONFIG_GPIO_MAX3191X) += gpio-max3191x.o
obj-$(CONFIG_GPIO_MAX7300) += gpio-max7300.o
diff --git a/drivers/gpio/gpio-macsmc.c b/drivers/gpio/gpio-macsmc.c
new file mode 100644
index 0000000..98fc74a
--- /dev/null
+++ b/drivers/gpio/gpio-macsmc.c
@@ -0,0 +1,388 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple SMC GPIO driver
+ * Copyright The Asahi Linux Contributors
+ *
+ * This driver implements basic SMC PMU GPIO support that can read inputs
+ * and write outputs. Mode changes and IRQ config are not yet implemented.
+ */
+
+#include <linux/bitmap.h>
+#include <linux/device.h>
+#include <linux/gpio/driver.h>
+#include <linux/irq.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/macsmc.h>
+
+#define MAX_GPIO 64
+
+/*
+ * Commands 0-6 are, presumably, the intended API.
+ * Command 0xff lets you get/set the pin configuration in detail directly,
+ * but the bit meanings seem not to be stable between devices/PMU hardware
+ * versions.
+ *
+ * We're going to try to make do with the low commands for now.
+ * We don't implement pin mode changes at this time.
+ */
+
+#define CMD_ACTION (0 << 24)
+#define CMD_OUTPUT (1 << 24)
+#define CMD_INPUT (2 << 24)
+#define CMD_PINMODE (3 << 24)
+#define CMD_IRQ_ENABLE (4 << 24)
+#define CMD_IRQ_ACK (5 << 24)
+#define CMD_IRQ_MODE (6 << 24)
+#define CMD_CONFIG (0xff << 24)
+
+#define MODE_INPUT 0
+#define MODE_OUTPUT 1
+#define MODE_VALUE_0 0
+#define MODE_VALUE_1 2
+
+#define IRQ_MODE_HIGH 0
+#define IRQ_MODE_LOW 1
+#define IRQ_MODE_RISING 2
+#define IRQ_MODE_FALLING 3
+#define IRQ_MODE_BOTH 4
+
+#define CONFIG_MASK GENMASK(23, 16)
+#define CONFIG_VAL GENMASK(7, 0)
+
+#define CONFIG_OUTMODE GENMASK(7, 6)
+#define CONFIG_IRQMODE GENMASK(5, 3)
+#define CONFIG_PULLDOWN BIT(2)
+#define CONFIG_PULLUP BIT(1)
+#define CONFIG_OUTVAL BIT(0)
+
+/*
+ * output modes seem to differ depending on the PMU in use... ?
+ * j274 / M1 (Sera PMU):
+ * 0 = input
+ * 1 = output
+ * 2 = open drain
+ * 3 = disable
+ * j314 / M1Pro (Maverick PMU):
+ * 0 = input
+ * 1 = open drain
+ * 2 = output
+ * 3 = ?
+ */
+
+#define SMC_EV_GPIO 0x7202
+
+struct macsmc_gpio {
+ struct device *dev;
+ struct apple_smc *smc;
+ struct gpio_chip gc;
+ struct irq_chip ic;
+ struct notifier_block nb;
+
+ struct mutex irq_mutex;
+ DECLARE_BITMAP(irq_supported, MAX_GPIO);
+ DECLARE_BITMAP(irq_enable_shadow, MAX_GPIO);
+ DECLARE_BITMAP(irq_enable, MAX_GPIO);
+ u32 irq_mode_shadow[MAX_GPIO];
+ u32 irq_mode[MAX_GPIO];
+
+ int first_index;
+};
+
+static int macsmc_gpio_nr(smc_key key)
+{
+ int low = hex_to_bin(key & 0xff);
+ int high = hex_to_bin((key >> 8) & 0xff);
+
+ if (low < 0 || high < 0)
+ return -1;
+
+ return low | (high << 4);
+}
+
+static int macsmc_gpio_key(unsigned int offset)
+{
+ return _SMC_KEY("gP\0\0") | (hex_asc_hi(offset) << 8) | hex_asc_lo(offset);
+}
+
+static int macsmc_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
+{
+ struct macsmc_gpio *smcgp = gpiochip_get_data(gc);
+ smc_key key = macsmc_gpio_key(offset);
+ u32 val;
+ int ret;
+
+ /* First try reading the explicit pin mode register */
+ ret = apple_smc_rw_u32(smcgp->smc, key, CMD_PINMODE, &val);
+ if (!ret)
+ return (val & MODE_OUTPUT) ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN;
+
+ /*
+ * Less common IRQ configs cause CMD_PINMODE to fail, and so does open drain mode.
+ * Fall back to reading IRQ mode, which will only succeed for inputs.
+ */
+ ret = apple_smc_rw_u32(smcgp->smc, key, CMD_IRQ_MODE, &val);
+ return (!ret) ? GPIO_LINE_DIRECTION_IN : GPIO_LINE_DIRECTION_OUT;
+}
+
+static int macsmc_gpio_get(struct gpio_chip *gc, unsigned int offset)
+{
+ struct macsmc_gpio *smcgp = gpiochip_get_data(gc);
+ smc_key key = macsmc_gpio_key(offset);
+ u32 val;
+ int ret;
+
+ ret = macsmc_gpio_get_direction(gc, offset);
+ if (ret < 0)
+ return ret;
+
+ if (ret == GPIO_LINE_DIRECTION_OUT)
+ ret = apple_smc_rw_u32(smcgp->smc, key, CMD_OUTPUT, &val);
+ else
+ ret = apple_smc_rw_u32(smcgp->smc, key, CMD_INPUT, &val);
+
+ if (ret < 0)
+ return ret;
+
+ return val ? 1 : 0;
+}
+
+static void macsmc_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
+{
+ struct macsmc_gpio *smcgp = gpiochip_get_data(gc);
+ smc_key key = macsmc_gpio_key(offset);
+ int ret;
+
+ value |= CMD_OUTPUT;
+ ret = apple_smc_write_u32(smcgp->smc, key, CMD_OUTPUT | value);
+ if (ret < 0)
+ dev_err(smcgp->dev, "GPIO set failed %p4ch = 0x%x\n", &key, value);
+}
+
+static int macsmc_gpio_init_valid_mask(struct gpio_chip *gc,
+ unsigned long *valid_mask, unsigned int ngpios)
+{
+ struct macsmc_gpio *smcgp = gpiochip_get_data(gc);
+ int count = apple_smc_get_key_count(smcgp->smc) - smcgp->first_index;
+ int i;
+
+ if (count > MAX_GPIO)
+ count = MAX_GPIO;
+
+ bitmap_zero(valid_mask, ngpios);
+
+ for (i = 0; i < count; i++) {
+ smc_key key;
+ int gpio_nr;
+ u32 val;
+ int ret = apple_smc_get_key_by_index(smcgp->smc, smcgp->first_index + i, &key);
+
+ if (ret < 0)
+ return ret;
+
+ if (key > SMC_KEY(gPff))
+ break;
+
+ gpio_nr = macsmc_gpio_nr(key);
+ if (gpio_nr < 0 || gpio_nr > MAX_GPIO) {
+ dev_err(smcgp->dev, "Bad GPIO key %p4ch\n", &key);
+ continue;
+ }
+
+ set_bit(gpio_nr, valid_mask);
+
+ /* Check for IRQ support */
+ ret = apple_smc_rw_u32(smcgp->smc, key, CMD_IRQ_MODE, &val);
+ if (!ret)
+ set_bit(gpio_nr, smcgp->irq_supported);
+ }
+
+ return 0;
+}
+
+static int macsmc_gpio_event(struct notifier_block *nb, unsigned long event, void *data)
+{
+ struct macsmc_gpio *smcgp = container_of(nb, struct macsmc_gpio, nb);
+ u16 type = event >> 16;
+ u8 offset = (event >> 8) & 0xff;
+ smc_key key = macsmc_gpio_key(offset);
+ unsigned long flags;
+
+ if (type != SMC_EV_GPIO)
+ return NOTIFY_DONE;
+
+ if (offset > MAX_GPIO) {
+ dev_err(smcgp->dev, "GPIO event index %d out of range\n", offset);
+ return NOTIFY_BAD;
+ }
+
+ local_irq_save(flags);
+ generic_handle_irq_desc(irq_resolve_mapping(smcgp->gc.irq.domain, offset));
+ local_irq_restore(flags);
+
+ if (apple_smc_write_u32(smcgp->smc, key, CMD_IRQ_ACK | 1) < 0)
+ dev_err(smcgp->dev, "GPIO IRQ ack failed for %p4ch\n", &key);
+
+ return NOTIFY_OK;
+}
+
+static void macsmc_gpio_irq_enable(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct macsmc_gpio *smcgp = gpiochip_get_data(gc);
+
+ set_bit(irqd_to_hwirq(d), smcgp->irq_enable_shadow);
+}
+
+static void macsmc_gpio_irq_disable(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct macsmc_gpio *smcgp = gpiochip_get_data(gc);
+
+ clear_bit(irqd_to_hwirq(d), smcgp->irq_enable_shadow);
+}
+
+static int macsmc_gpio_irq_set_type(struct irq_data *d, unsigned int type)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct macsmc_gpio *smcgp = gpiochip_get_data(gc);
+ int offset = irqd_to_hwirq(d);
+ u32 mode;
+
+ if (!test_bit(offset, smcgp->irq_supported))
+ return -EINVAL;
+
+ switch (type & IRQ_TYPE_SENSE_MASK) {
+ case IRQ_TYPE_LEVEL_HIGH:
+ mode = IRQ_MODE_HIGH;
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ mode = IRQ_MODE_LOW;
+ break;
+ case IRQ_TYPE_EDGE_RISING:
+ mode = IRQ_MODE_RISING;
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ mode = IRQ_MODE_FALLING;
+ break;
+ case IRQ_TYPE_EDGE_BOTH:
+ mode = IRQ_MODE_BOTH;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ smcgp->irq_mode_shadow[offset] = mode;
+ return 0;
+}
+
+static void macsmc_gpio_irq_bus_lock(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct macsmc_gpio *smcgp = gpiochip_get_data(gc);
+
+ mutex_lock(&smcgp->irq_mutex);
+}
+
+static void macsmc_gpio_irq_bus_sync_unlock(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct macsmc_gpio *smcgp = gpiochip_get_data(gc);
+ smc_key key = macsmc_gpio_key(irqd_to_hwirq(d));
+ int offset = irqd_to_hwirq(d);
+ bool val;
+
+ if (smcgp->irq_mode_shadow[offset] != smcgp->irq_mode[offset]) {
+ u32 cmd = CMD_IRQ_MODE | smcgp->irq_mode_shadow[offset];
+ if (apple_smc_write_u32(smcgp->smc, key, cmd) < 0)
+ dev_err(smcgp->dev, "GPIO IRQ config failed for %p4ch = 0x%x\n", &key, cmd);
+ else
+ smcgp->irq_mode_shadow[offset] = smcgp->irq_mode[offset];
+ }
+
+ val = test_bit(offset, smcgp->irq_enable_shadow);
+ if (test_bit(offset, smcgp->irq_enable) != val) {
+ if (apple_smc_write_u32(smcgp->smc, key, CMD_IRQ_ENABLE | val) < 0)
+ dev_err(smcgp->dev, "GPIO IRQ en/disable failed for %p4ch\n", &key);
+ else
+ change_bit(offset, smcgp->irq_enable);
+ }
+
+ mutex_unlock(&smcgp->irq_mutex);
+}
+
+static int macsmc_gpio_probe(struct platform_device *pdev)
+{
+ struct macsmc_gpio *smcgp;
+ struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent);
+ smc_key key;
+ int ret;
+
+ smcgp = devm_kzalloc(&pdev->dev, sizeof(*smcgp), GFP_KERNEL);
+ if (!smcgp)
+ return -ENOMEM;
+
+ pdev->dev.of_node = of_get_child_by_name(pdev->dev.parent->of_node, "gpio");
+
+ smcgp->dev = &pdev->dev;
+ smcgp->smc = smc;
+ smcgp->first_index = apple_smc_find_first_key_index(smc, SMC_KEY(gP00));
+
+ if (smcgp->first_index >= apple_smc_get_key_count(smc))
+ return -ENODEV;
+
+ ret = apple_smc_get_key_by_index(smc, smcgp->first_index, &key);
+ if (ret < 0)
+ return ret;
+
+ if (key > macsmc_gpio_key(MAX_GPIO - 1))
+ return -ENODEV;
+
+ dev_info(smcgp->dev, "First GPIO key: %p4ch\n", &key);
+
+ smcgp->gc.label = "macsmc-pmu-gpio";
+ smcgp->gc.owner = THIS_MODULE;
+ smcgp->gc.get = macsmc_gpio_get;
+ smcgp->gc.set = macsmc_gpio_set;
+ smcgp->gc.get_direction = macsmc_gpio_get_direction;
+ smcgp->gc.init_valid_mask = macsmc_gpio_init_valid_mask;
+ smcgp->gc.can_sleep = true;
+ smcgp->gc.ngpio = MAX_GPIO;
+ smcgp->gc.base = -1;
+ smcgp->gc.parent = &pdev->dev;
+
+ smcgp->ic.name = "macsmc-pmu-gpio";
+ smcgp->ic.irq_mask = macsmc_gpio_irq_disable;
+ smcgp->ic.irq_unmask = macsmc_gpio_irq_enable;
+ smcgp->ic.irq_set_type = macsmc_gpio_irq_set_type;
+ smcgp->ic.irq_bus_lock = macsmc_gpio_irq_bus_lock;
+ smcgp->ic.irq_bus_sync_unlock = macsmc_gpio_irq_bus_sync_unlock;
+ smcgp->ic.irq_set_type = macsmc_gpio_irq_set_type;
+ smcgp->ic.flags = IRQCHIP_SET_TYPE_MASKED | IRQCHIP_MASK_ON_SUSPEND;
+
+ smcgp->gc.irq.chip = &smcgp->ic;
+ smcgp->gc.irq.parent_handler = NULL;
+ smcgp->gc.irq.num_parents = 0;
+ smcgp->gc.irq.parents = NULL;
+ smcgp->gc.irq.default_type = IRQ_TYPE_NONE;
+ smcgp->gc.irq.handler = handle_simple_irq;
+
+ mutex_init(&smcgp->irq_mutex);
+
+ smcgp->nb.notifier_call = macsmc_gpio_event;
+ apple_smc_register_notifier(smc, &smcgp->nb);
+
+ return devm_gpiochip_add_data(&pdev->dev, &smcgp->gc, smcgp);
+}
+
+static struct platform_driver macsmc_gpio_driver = {
+ .driver = {
+ .name = "macsmc-gpio",
+ },
+ .probe = macsmc_gpio_probe,
+};
+module_platform_driver(macsmc_gpio_driver);
+
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_DESCRIPTION("Apple SMC GPIO driver");
+MODULE_ALIAS("platform:macsmc-gpio");
diff --git a/drivers/gpu/drm/tiny/Kconfig b/drivers/gpu/drm/tiny/Kconfig
index f6889f6..097c2fa 100644
--- a/drivers/gpu/drm/tiny/Kconfig
+++ b/drivers/gpu/drm/tiny/Kconfig
@@ -85,6 +85,7 @@
select APERTURE_HELPERS
select DRM_GEM_SHMEM_HELPER
select DRM_KMS_HELPER
+ select BACKLIGHT_CLASS_DEVICE
help
DRM driver for simple platform-provided framebuffers.
@@ -96,6 +97,13 @@
On x86 BIOS or UEFI systems, you should also select SYSFB_SIMPLEFB
to use UEFI and VESA framebuffers.
+config DRM_SIMPLEDRM_BACKLIGHT
+ bool "Backlight support for simpledrm"
+ depends on DRM_SIMPLEDRM && !DRM_APPLE
+ select BACKLIGHT_CLASS_DEVICE
+ help
+ Enable backlight support for simpledrm.
+
config TINYDRM_HX8357D
tristate "DRM support for HX8357D display panels"
depends on DRM && SPI
diff --git a/drivers/gpu/drm/tiny/simpledrm.c b/drivers/gpu/drm/tiny/simpledrm.c
index 5fefc89..85805d1 100644
--- a/drivers/gpu/drm/tiny/simpledrm.c
+++ b/drivers/gpu/drm/tiny/simpledrm.c
@@ -1,5 +1,8 @@
// SPDX-License-Identifier: GPL-2.0-only
+#if defined CONFIG_DRM_SIMPLEDRM_BACKLIGHT
+#include <linux/backlight.h>
+#endif
#include <linux/clk.h>
#include <linux/of_clk.h>
#include <linux/minmax.h>
@@ -250,6 +253,10 @@ struct simpledrm_device {
struct drm_crtc crtc;
struct drm_encoder encoder;
struct drm_connector connector;
+#if defined CONFIG_DRM_SIMPLEDRM_BACKLIGHT
+ /* backlight */
+ struct backlight_device *backlight;
+#endif
};
static struct simpledrm_device *simpledrm_device_of_dev(struct drm_device *dev)
@@ -655,6 +662,28 @@ static enum drm_mode_status simpledrm_crtc_helper_mode_valid(struct drm_crtc *cr
return drm_crtc_helper_mode_valid_fixed(crtc, mode, &sdev->mode);
}
+#if defined CONFIG_DRM_SIMPLEDRM_BACKLIGHT
+static void simpledrm_crtc_helper_atomic_enable(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ struct drm_device *dev = crtc->dev;
+ struct simpledrm_device *sdev = simpledrm_device_of_dev(dev);
+
+ if (sdev->backlight)
+ backlight_enable(sdev->backlight);
+}
+
+static void simpledrm_crtc_helper_atomic_disable(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ struct drm_device *dev = crtc->dev;
+ struct simpledrm_device *sdev = simpledrm_device_of_dev(dev);
+
+ if (sdev->backlight)
+ backlight_disable(sdev->backlight);
+}
+#endif
+
/*
* The CRTC is always enabled. Screen updates are performed by
* the primary plane's atomic_update function. Disabling clears
@@ -663,6 +692,10 @@ static enum drm_mode_status simpledrm_crtc_helper_mode_valid(struct drm_crtc *cr
static const struct drm_crtc_helper_funcs simpledrm_crtc_helper_funcs = {
.mode_valid = simpledrm_crtc_helper_mode_valid,
.atomic_check = drm_crtc_helper_atomic_check,
+#if defined CONFIG_DRM_SIMPLEDRM_BACKLIGHT
+ .atomic_enable = simpledrm_crtc_helper_atomic_enable,
+ .atomic_disable = simpledrm_crtc_helper_atomic_disable,
+#endif
};
static const struct drm_crtc_funcs simpledrm_crtc_funcs = {
@@ -749,6 +782,11 @@ static struct simpledrm_device *simpledrm_device_create(struct drm_driver *drv,
* Hardware settings
*/
+#if defined CONFIG_DRM_SIMPLEDRM_BACKLIGHT
+ sdev->backlight = devm_of_find_backlight(&pdev->dev);
+ if (IS_ERR(sdev->backlight))
+ sdev->backlight = NULL;
+#endif
ret = simpledrm_device_init_clocks(sdev);
if (ret)
return ERR_PTR(ret);
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 4ce74af..7f78e57 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -129,7 +129,7 @@
tristate "Apple {i,Power,Mac}Books"
depends on LEDS_CLASS
depends on NEW_LEDS
- default !EXPERT
+ default !EXPERT || SPI_HID_APPLE
help
Support for some Apple devices which less or more break
HID specification.
@@ -669,11 +669,13 @@
config HID_MAGICMOUSE
tristate "Apple Magic Mouse/Trackpad multi-touch support"
+ default SPI_HID_APPLE
help
Support for the Apple Magic Mouse/Trackpad multi-touch.
Say Y here if you want support for the multi-touch features of the
- Apple Wireless "Magic" Mouse and the Apple Wireless "Magic" Trackpad.
+ Apple Wireless "Magic" Mouse, the Apple Wireless "Magic" Trackpad and
+ force touch Trackpads in Macbooks starting from 2015.
config HID_MALTRON
tristate "Maltron L90 keyboard"
@@ -1341,4 +1343,8 @@
source "drivers/hid/surface-hid/Kconfig"
+source "drivers/hid/spi-hid/Kconfig"
+
+source "drivers/hid/dockchannel-hid/Kconfig"
+
endif # HID_SUPPORT
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 8a06d0f..79f6d6d7 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -169,3 +169,7 @@
obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/
obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/
+
+obj-$(CONFIG_SPI_HID_APPLE_CORE) += spi-hid/
+
+obj-$(CONFIG_HID_DOCKCHANNEL) += dockchannel-hid/
diff --git a/drivers/hid/dockchannel-hid/Kconfig b/drivers/hid/dockchannel-hid/Kconfig
new file mode 100644
index 0000000..8a81d551
--- /dev/null
+++ b/drivers/hid/dockchannel-hid/Kconfig
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0-only OR MIT
+menu "DockChannel HID support"
+ depends on APPLE_DOCKCHANNEL
+
+config HID_DOCKCHANNEL
+ tristate "HID over DockChannel transport layer for Apple Silicon SoCs"
+ default ARCH_APPLE
+ depends on APPLE_DOCKCHANNEL && INPUT && OF && HID
+ help
+ Say Y here if you use an M2 or later Apple Silicon based laptop.
+ The keyboard and touchpad are HID based devices connected via the
+ proprietary DockChannel interface.
+
+endmenu
diff --git a/drivers/hid/dockchannel-hid/Makefile b/drivers/hid/dockchannel-hid/Makefile
new file mode 100644
index 0000000..7dba766
--- /dev/null
+++ b/drivers/hid/dockchannel-hid/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-only OR MIT
+#
+# Makefile for DockChannel HID transport drivers
+#
+
+obj-$(CONFIG_HID_DOCKCHANNEL) += dockchannel-hid.o
diff --git a/drivers/hid/dockchannel-hid/dockchannel-hid.c b/drivers/hid/dockchannel-hid/dockchannel-hid.c
new file mode 100644
index 0000000..6589917
--- /dev/null
+++ b/drivers/hid/dockchannel-hid/dockchannel-hid.c
@@ -0,0 +1,1216 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0 OR MIT
+ *
+ * Apple DockChannel HID transport driver
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+#include <asm/unaligned.h>
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/firmware.h>
+#include <linux/gpio/consumer.h>
+#include <linux/hid.h>
+#include <linux/slab.h>
+#include <linux/soc/apple/dockchannel.h>
+#include <linux/of.h>
+#include "../hid-ids.h"
+
+#define COMMAND_TIMEOUT_MS 1000
+#define START_TIMEOUT_MS 2000
+
+#define MAX_INTERFACES 16
+
+/* Data + checksum */
+#define MAX_PKT_SIZE (0xffff + 4)
+
+#define DCHID_CHANNEL_CMD 0x11
+#define DCHID_CHANNEL_REPORT 0x12
+
+struct dchid_hdr {
+ u8 hdr_len;
+ u8 channel;
+ u16 length;
+ u8 seq;
+ u8 iface;
+ u16 pad;
+} __packed;
+
+#define IFACE_COMM 0
+
+#define FLAGS_GROUP GENMASK(7, 6)
+#define FLAGS_REQ GENMASK(5, 0)
+
+#define GROUP_INPUT 0
+#define GROUP_OUTPUT 1
+#define GROUP_CMD 2
+
+#define REQ_SET_REPORT 0
+#define REQ_GET_REPORT 1
+
+struct dchid_subhdr {
+ u8 flags;
+ u8 unk;
+ u16 length;
+ u32 retcode;
+} __packed;
+
+#define EVENT_GPIO_CMD 0xa0
+#define EVENT_INIT 0xf0
+#define EVENT_READY 0xf1
+
+struct dchid_init_hdr {
+ u8 type;
+ u8 unk1;
+ u8 unk2;
+ u8 iface;
+ char name[16];
+ u8 more_packets;
+ u8 unkpad;
+} __packed;
+
+#define INIT_HID_DESCRIPTOR 0
+#define INIT_GPIO_REQUEST 1
+#define INIT_TERMINATOR 2
+#define INIT_PRODUCT_NAME 7
+
+#define CMD_RESET_INTERFACE 0x40
+#define CMD_SEND_FIRMWARE 0x95
+#define CMD_ENABLE_INTERFACE 0xb4
+#define CMD_ACK_GPIO_CMD 0xa1
+
+struct dchid_init_block_hdr {
+ u16 type;
+ u16 length;
+} __packed;
+
+#define MAX_GPIO_NAME 32
+
+struct dchid_gpio_request {
+ u16 unk;
+ u16 id;
+ char name[MAX_GPIO_NAME];
+} __packed;
+
+struct dchid_gpio_cmd {
+ u8 type;
+ u8 iface;
+ u8 gpio;
+ u8 unk;
+ u8 cmd;
+} __packed;
+
+struct dchid_gpio_ack {
+ u8 type;
+ u32 retcode;
+ u8 cmd[];
+} __packed;
+
+#define STM_REPORT_ID 0x10
+#define STM_REPORT_SERIAL 0x11
+#define STM_REPORT_KEYBTYPE 0x14
+
+struct dchid_stm_id {
+ u8 unk;
+ u16 vendor_id;
+ u16 product_id;
+ u16 version_number;
+ u8 unk2;
+ u8 unk3;
+ u8 keyboard_type;
+ u8 serial_length;
+ /* Serial follows, but we grab it with a different report. */
+} __packed;
+
+#define FW_MAGIC 0x46444948
+#define FW_VER 1
+
+struct fw_header {
+ u32 magic;
+ u32 version;
+ u32 hdr_length;
+ u32 data_length;
+ u32 iface_offset;
+} __packed;
+
+struct dchid_work {
+ struct work_struct work;
+ struct dchid_iface *iface;
+
+ struct dchid_hdr hdr;
+ u8 data[];
+};
+
+struct dchid_iface {
+ struct dockchannel_hid *dchid;
+ struct hid_device *hid;
+ struct workqueue_struct *wq;
+
+ bool creating;
+ struct work_struct create_work;
+
+ int index;
+ const char *name;
+ const struct device_node *of_node;
+
+ uint8_t tx_seq;
+ bool deferred;
+ bool starting;
+ bool open;
+ struct completion ready;
+
+ void *hid_desc;
+ size_t hid_desc_len;
+
+ struct gpio_desc *gpio;
+ char gpio_name[MAX_GPIO_NAME];
+ int gpio_id;
+
+ struct mutex out_mutex;
+ u32 out_flags;
+ int out_report;
+ u32 retcode;
+ void *resp_buf;
+ size_t resp_size;
+ struct completion out_complete;
+
+ u32 keyboard_layout_id;
+};
+
+struct dockchannel_hid {
+ struct device *dev;
+ struct dockchannel *dc;
+ struct device_link *helper_link;
+
+ bool id_ready;
+ struct dchid_stm_id device_id;
+ char serial[64];
+
+ struct dchid_iface *comm;
+ struct dchid_iface *ifaces[MAX_INTERFACES];
+
+ u8 pkt_buf[MAX_PKT_SIZE];
+
+ /* Workqueue to asynchronously create HID devices */
+ struct workqueue_struct *new_iface_wq;
+};
+
+static ssize_t apple_layout_id_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ struct dchid_iface *iface = hdev->driver_data;
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", iface->keyboard_layout_id);
+}
+
+static DEVICE_ATTR_RO(apple_layout_id);
+
+static struct dchid_iface *
+dchid_get_interface(struct dockchannel_hid *dchid, int index, const char *name)
+{
+ struct dchid_iface *iface;
+
+ if (index >= MAX_INTERFACES) {
+ dev_err(dchid->dev, "Interface index %d out of range\n", index);
+ return NULL;
+ }
+
+ if (dchid->ifaces[index])
+ return dchid->ifaces[index];
+
+ iface = devm_kzalloc(dchid->dev, sizeof(struct dchid_iface), GFP_KERNEL);
+ if (!iface)
+ return NULL;
+
+ iface->index = index;
+ iface->name = devm_kstrdup(dchid->dev, name, GFP_KERNEL);
+ iface->dchid = dchid;
+ iface->out_report= -1;
+ init_completion(&iface->out_complete);
+ init_completion(&iface->ready);
+ mutex_init(&iface->out_mutex);
+ iface->wq = alloc_ordered_workqueue("dchid-%s", WQ_MEM_RECLAIM, iface->name);
+ if (!iface->wq)
+ return NULL;
+
+ /* Comm is not a HID subdevice */
+ if (!strcmp(name, "comm")) {
+ dchid->ifaces[index] = iface;
+ return iface;
+ }
+
+ iface->of_node = of_get_child_by_name(dchid->dev->of_node, name);
+ if (!iface->of_node) {
+ dev_warn(dchid->dev, "No OF node for subdevice %s, ignoring.", name);
+ return NULL;
+ }
+
+ dchid->ifaces[index] = iface;
+ return iface;
+}
+
+static u32 dchid_checksum(void *p, size_t length)
+{
+ u32 sum = 0;
+
+ while (length >= 4) {
+ sum += get_unaligned_le32(p);
+ p += 4;
+ length -= 4;
+ }
+
+ WARN_ON_ONCE(length);
+ return sum;
+}
+
+static int dchid_send(struct dchid_iface *iface, u32 flags, void *msg, size_t size)
+{
+ u32 checksum = 0xffffffff;
+ size_t wsize = round_down(size, 4);
+ size_t tsize = size - wsize;
+ int ret;
+ struct {
+ struct dchid_hdr hdr;
+ struct dchid_subhdr sub;
+ } __packed h;
+
+ memset(&h, 0, sizeof(h));
+ h.hdr.hdr_len = sizeof(h.hdr);
+ h.hdr.channel = DCHID_CHANNEL_CMD;
+ h.hdr.length = round_up(size, 4) + sizeof(h.sub);
+ h.hdr.seq = iface->tx_seq;
+ h.hdr.iface = iface->index;
+ h.sub.flags = flags;
+ h.sub.length = size;
+
+ ret = dockchannel_send(iface->dchid->dc, &h, sizeof(h));
+ if (ret < 0)
+ return ret;
+ checksum -= dchid_checksum(&h, sizeof(h));
+
+ ret = dockchannel_send(iface->dchid->dc, msg, wsize);
+ if (ret < 0)
+ return ret;
+ checksum -= dchid_checksum(msg, wsize);
+
+ if (tsize) {
+ u8 tail[4] = {0, 0, 0, 0};
+
+ memcpy(tail, msg + wsize, tsize);
+ ret = dockchannel_send(iface->dchid->dc, tail, sizeof(tail));
+ if (ret < 0)
+ return ret;
+ checksum -= dchid_checksum(tail, sizeof(tail));
+ }
+
+ ret = dockchannel_send(iface->dchid->dc, &checksum, sizeof(checksum));
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int dchid_cmd(struct dchid_iface *iface, u32 type, u32 req,
+ void *data, size_t size, void *resp_buf, size_t resp_size)
+{
+ int ret;
+ int report_id = *(u8*)data;
+
+ mutex_lock(&iface->out_mutex);
+
+ WARN_ON(iface->out_report != -1);
+ iface->out_report = report_id;
+ iface->out_flags = FIELD_PREP(FLAGS_GROUP, type) | FIELD_PREP(FLAGS_REQ, req);
+ iface->resp_buf = resp_buf;
+ iface->resp_size = resp_size;
+ reinit_completion(&iface->out_complete);
+
+ ret = dchid_send(iface, iface->out_flags, data, size);
+ if (ret < 0)
+ goto done;
+
+ if (!wait_for_completion_timeout(&iface->out_complete, msecs_to_jiffies(COMMAND_TIMEOUT_MS))) {
+ dev_err(iface->dchid->dev, "output report 0x%x to iface %d (%s) timed out\n",
+ report_id, iface->index, iface->name);
+ ret = -ETIMEDOUT;
+ goto done;
+ }
+
+ ret = iface->resp_size;
+ if (iface->retcode) {
+ dev_err(iface->dchid->dev,
+ "output report 0x%x to iface %d (%s) failed with err 0x%x\n",
+ report_id, iface->index, iface->name, iface->retcode);
+ ret = -EIO;
+ }
+
+done:
+ iface->tx_seq++;
+ iface->out_report = -1;
+ iface->out_flags = 0;
+ iface->resp_buf = NULL;
+ iface->resp_size = 0;
+ mutex_unlock(&iface->out_mutex);
+ return ret;
+}
+
+static int dchid_comm_cmd(struct dockchannel_hid *dchid, void *cmd, size_t size)
+{
+ return dchid_cmd(dchid->comm, GROUP_CMD, REQ_SET_REPORT, cmd, size, NULL, 0);
+}
+
+static int dchid_enable_interface(struct dchid_iface *iface)
+{
+ u8 msg[] = { CMD_ENABLE_INTERFACE, iface->index };
+
+ return dchid_comm_cmd(iface->dchid, msg, sizeof(msg));
+}
+
+static int dchid_reset_interface(struct dchid_iface *iface, int state)
+{
+ u8 msg[] = { CMD_RESET_INTERFACE, 1, iface->index, state };
+
+ return dchid_comm_cmd(iface->dchid, msg, sizeof(msg));
+}
+
+static int dchid_send_firmware(struct dchid_iface *iface, void *firmware, size_t size)
+{
+ struct {
+ u8 cmd;
+ u8 unk1;
+ u8 unk2;
+ u8 iface;
+ u64 addr;
+ u32 size;
+ } __packed msg = {
+ .cmd = CMD_SEND_FIRMWARE,
+ .unk1 = 2,
+ .unk2 = 0,
+ .iface = iface->index,
+ .size = size,
+ };
+ dma_addr_t addr;
+ void *buf = dmam_alloc_coherent(iface->dchid->dev, size, &addr, GFP_KERNEL);
+
+ if (IS_ERR_OR_NULL(buf))
+ return buf ? PTR_ERR(buf) : -ENOMEM;
+
+ msg.addr = addr;
+ memcpy(buf, firmware, size);
+ wmb();
+
+ return dchid_comm_cmd(iface->dchid, &msg, sizeof(msg));
+}
+
+static int dchid_get_firmware(struct dchid_iface *iface, void **firmware, size_t *size)
+{
+ int ret;
+ const char *fw_name;
+ const struct firmware *fw;
+ struct fw_header *hdr;
+ u8 *fw_data;
+
+ ret = of_property_read_string(iface->of_node, "firmware-name", &fw_name);
+ if (ret) {
+ /* Firmware is only for some devices */
+ *firmware = NULL;
+ *size = 0;
+ return 0;
+ }
+
+ ret = request_firmware(&fw, fw_name, iface->dchid->dev);
+ if (ret)
+ return ret;
+
+ hdr = (struct fw_header *)fw->data;
+
+ if (hdr->magic != FW_MAGIC || hdr->version != FW_VER ||
+ hdr->hdr_length < sizeof(*hdr) || hdr->hdr_length > fw->size ||
+ (hdr->hdr_length + (size_t)hdr->data_length) > fw->size ||
+ hdr->iface_offset >= hdr->data_length) {
+ dev_warn(iface->dchid->dev, "%s: invalid firmware header\n",
+ fw_name);
+ ret = -EINVAL;
+ goto done;
+ }
+
+ fw_data = devm_kmemdup(iface->dchid->dev, fw->data + hdr->hdr_length,
+ hdr->data_length, GFP_KERNEL);
+ if (!fw_data) {
+ ret = -ENOMEM;
+ goto done;
+ }
+
+ if (hdr->iface_offset)
+ fw_data[hdr->iface_offset] = iface->index;
+
+ *firmware = fw_data;
+ *size = hdr->data_length;
+
+done:
+ release_firmware(fw);
+ return ret;
+}
+
+static int dchid_request_gpio(struct dchid_iface *iface)
+{
+ char prop_name[MAX_GPIO_NAME + 16];
+
+ if (iface->gpio)
+ return 0;
+
+ dev_info(iface->dchid->dev, "Requesting GPIO %s#%d: %s\n",
+ iface->name, iface->gpio_id, iface->gpio_name);
+
+ snprintf(prop_name, sizeof(prop_name), "apple,%s", iface->gpio_name);
+
+ iface->gpio = devm_gpiod_get_index(iface->dchid->dev, prop_name, 0, GPIOD_OUT_LOW);
+
+ if (IS_ERR_OR_NULL(iface->gpio)) {
+ dev_err(iface->dchid->dev, "Failed to request GPIO %s-gpios\n", prop_name);
+ iface->gpio = NULL;
+ return -1;
+ }
+
+ return 0;
+}
+
+static int dchid_start_interface(struct dchid_iface *iface)
+{
+ void *fw;
+ size_t size;
+ int ret;
+
+ if (iface->starting) {
+ dev_warn(iface->dchid->dev, "Interface %s is already starting", iface->name);
+ return -EINPROGRESS;
+ }
+
+ dev_info(iface->dchid->dev, "Starting interface %s\n", iface->name);
+
+ iface->starting = true;
+
+ /* Look to see if we need firmware */
+ ret = dchid_get_firmware(iface, &fw, &size);
+ if (ret < 0)
+ goto err;
+
+ /* If we need a GPIO, make sure we have it. */
+ if (iface->gpio_id) {
+ ret = dchid_request_gpio(iface);
+ if (ret < 0)
+ goto err;
+ }
+
+ /* Only multi-touch has firmware */
+ if (fw && size) {
+
+ /* Send firmware to the device */
+ dev_info(iface->dchid->dev, "Sending firmware for %s\n", iface->name);
+ ret = dchid_send_firmware(iface, fw, size);
+ if (ret < 0) {
+ dev_err(iface->dchid->dev, "Failed to send %s firmwareS", iface->name);
+ goto err;
+ }
+
+ /* After loading firmware, multi-touch needs a reset */
+ dev_info(iface->dchid->dev, "Resetting %s\n", iface->name);
+ dchid_reset_interface(iface, 0);
+ dchid_reset_interface(iface, 2);
+ }
+
+ return 0;
+
+err:
+ iface->starting = false;
+ return ret;
+}
+
+static int dchid_start(struct hid_device *hdev)
+{
+ struct dchid_iface *iface = hdev->driver_data;
+
+ if (iface->keyboard_layout_id) {
+ int ret = device_create_file(&hdev->dev, &dev_attr_apple_layout_id);
+ if (ret) {
+ dev_warn(iface->dchid->dev, "Failed to create apple_layout_id: %d", ret);
+ iface->keyboard_layout_id = 0;
+ }
+ }
+
+ return 0;
+};
+
+static void dchid_stop(struct hid_device *hdev)
+{
+ struct dchid_iface *iface = hdev->driver_data;
+
+ if (iface->keyboard_layout_id)
+ device_remove_file(&hdev->dev, &dev_attr_apple_layout_id);
+}
+
+static int dchid_open(struct hid_device *hdev)
+{
+ struct dchid_iface *iface = hdev->driver_data;
+ int ret;
+
+ if (!completion_done(&iface->ready)) {
+ ret = dchid_start_interface(iface);
+ if (ret < 0)
+ return ret;
+
+ if (!wait_for_completion_timeout(&iface->ready, msecs_to_jiffies(START_TIMEOUT_MS))) {
+ dev_err(iface->dchid->dev, "iface %s start timed out\n", iface->name);
+ return -ETIMEDOUT;
+ }
+ }
+
+ iface->open = true;
+ return 0;
+}
+
+static void dchid_close(struct hid_device *hdev)
+{
+ struct dchid_iface *iface = hdev->driver_data;
+
+ iface->open = false;
+}
+
+static int dchid_parse(struct hid_device *hdev)
+{
+ struct dchid_iface *iface = hdev->driver_data;
+
+ return hid_parse_report(hdev, iface->hid_desc, iface->hid_desc_len);
+}
+
+/* Note: buf excludes report number! For ease of fetching strings/etc. */
+static int dchid_get_report_cmd(struct dchid_iface *iface, u8 reportnum, void *buf, size_t len)
+{
+ int ret = dchid_cmd(iface, GROUP_CMD, REQ_GET_REPORT, &reportnum, 1, buf, len);
+
+ return ret <= 0 ? ret : ret - 1;
+}
+
+/* Note: buf includes report number! */
+static int dchid_set_report(struct dchid_iface *iface, void *buf, size_t len)
+{
+ return dchid_cmd(iface, GROUP_OUTPUT, REQ_SET_REPORT, buf, len, NULL, 0);
+}
+
+static int dchid_raw_request(struct hid_device *hdev,
+ unsigned char reportnum, __u8 *buf, size_t len,
+ unsigned char rtype, int reqtype)
+{
+ struct dchid_iface *iface = hdev->driver_data;
+
+ switch (reqtype) {
+ case HID_REQ_GET_REPORT:
+ buf[0] = reportnum;
+ return dchid_cmd(iface, GROUP_OUTPUT, REQ_GET_REPORT, &reportnum, 1, buf + 1, len - 1);
+ case HID_REQ_SET_REPORT:
+ return dchid_set_report(iface, buf, len);
+ default:
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static struct hid_ll_driver dchid_ll = {
+ .start = &dchid_start,
+ .stop = &dchid_stop,
+ .open = &dchid_open,
+ .close = &dchid_close,
+ .parse = &dchid_parse,
+ .raw_request = &dchid_raw_request,
+};
+
+static void dchid_create_interface_work(struct work_struct *ws)
+{
+ struct dchid_iface *iface = container_of(ws, struct dchid_iface, create_work);
+ struct dockchannel_hid *dchid = iface->dchid;
+ struct hid_device *hid;
+ int ret;
+
+ if (iface->hid) {
+ dev_warn(dchid->dev, "Interface %s already created!\n",
+ iface->name);
+ return;
+ }
+
+ dev_info(dchid->dev, "New interface %s\n", iface->name);
+
+ /* Start the interface. This is not the entire init process, as firmware is loaded later on device open. */
+ ret = dchid_enable_interface(iface);
+ if (ret < 0) {
+ dev_warn(dchid->dev, "Failed to enable %s: %d\n", iface->name, ret);
+ return;
+ }
+
+ iface->deferred = false;
+
+ hid = hid_allocate_device();
+ if (IS_ERR(hid))
+ return;
+
+ snprintf(hid->name, sizeof(hid->name), "Apple MTP %s", iface->name);
+ snprintf(hid->phys, sizeof(hid->phys), "%s.%d (%s)",
+ dev_name(dchid->dev), iface->index, iface->name);
+ strscpy(hid->uniq, dchid->serial, sizeof(hid->uniq));
+
+ hid->ll_driver = &dchid_ll;
+ hid->bus = BUS_HOST;
+ hid->vendor = dchid->device_id.vendor_id;
+ hid->product = dchid->device_id.product_id;
+ hid->version = dchid->device_id.version_number;
+ hid->type = HID_TYPE_OTHER;
+ if (!strcmp(iface->name, "multi-touch")) {
+ hid->type = HID_TYPE_SPI_MOUSE;
+ } else if (!strcmp(iface->name, "keyboard")) {
+ u32 country_code = 0;
+
+ hid->type = HID_TYPE_SPI_KEYBOARD;
+
+ /*
+ * We have to get the country code from the device tree, since the
+ * device provides no reliable way to get this info.
+ */
+ if (!of_property_read_u32(iface->of_node, "hid-country-code", &country_code))
+ hid->country = country_code;
+
+ of_property_read_u32(iface->of_node, "apple,keyboard-layout-id",
+ &iface->keyboard_layout_id);
+ }
+
+ hid->dev.parent = iface->dchid->dev;
+ hid->driver_data = iface;
+
+ iface->hid = hid;
+
+ ret = hid_add_device(hid);
+ if (ret < 0) {
+ iface->hid = NULL;
+ hid_destroy_device(hid);
+ dev_warn(iface->dchid->dev, "Failed to register hid device %s", iface->name);
+ }
+}
+
+static int dchid_create_interface(struct dchid_iface *iface)
+{
+ if (iface->creating)
+ return -EBUSY;
+
+ iface->creating = true;
+ INIT_WORK(&iface->create_work, dchid_create_interface_work);
+ return queue_work(iface->dchid->new_iface_wq, &iface->create_work);
+}
+
+static void dchid_handle_descriptor(struct dchid_iface *iface, void *hid_desc, size_t desc_len)
+{
+ if (iface->hid) {
+ dev_warn(iface->dchid->dev, "Tried to initialize already started interface %s!\n",
+ iface->name);
+ return;
+ }
+
+ iface->hid_desc = devm_kmemdup(iface->dchid->dev, hid_desc, desc_len, GFP_KERNEL);
+ if (!iface->hid_desc)
+ return;
+
+ iface->hid_desc_len = desc_len;
+}
+
+static void dchid_handle_ready(struct dockchannel_hid *dchid, void *data, size_t length)
+{
+ struct dchid_iface *iface;
+ u8 *pkt = data;
+ u8 index;
+ int i, ret;
+
+ if (length < 2) {
+ dev_err(dchid->dev, "Bad length for ready message: %zu\n", length);
+ return;
+ }
+
+ index = pkt[1];
+
+ if (index >= MAX_INTERFACES) {
+ dev_err(dchid->dev, "Got ready notification for bad iface %d\n", index);
+ return;
+ }
+
+ iface = dchid->ifaces[index];
+ if (!iface) {
+ dev_err(dchid->dev, "Got ready notification for unknown iface %d\n", index);
+ return;
+ }
+
+ dev_info(dchid->dev, "Interface %s is now ready\n", iface->name);
+ complete_all(&iface->ready);
+
+ /* When STM is ready, grab global device info */
+ if (!strcmp(iface->name, "stm")) {
+ ret = dchid_get_report_cmd(iface, STM_REPORT_ID, &dchid->device_id,
+ sizeof(dchid->device_id));
+ if (ret < sizeof(dchid->device_id)) {
+ dev_warn(iface->dchid->dev, "Failed to get device ID from STM!\n");
+ /* Fake it and keep going. Things might still work... */
+ memset(&dchid->device_id, 0, sizeof(dchid->device_id));
+ dchid->device_id.vendor_id = HOST_VENDOR_ID_APPLE;
+ }
+ ret = dchid_get_report_cmd(iface, STM_REPORT_SERIAL, dchid->serial,
+ sizeof(dchid->serial) - 1);
+ if (ret < 0) {
+ dev_warn(iface->dchid->dev, "Failed to get serial from STM!\n");
+ dchid->serial[0] = 0;
+ }
+
+ dchid->id_ready = true;
+ for (i = 0; i < MAX_INTERFACES; i++) {
+ if (!dchid->ifaces[i] || !dchid->ifaces[i]->deferred)
+ continue;
+ dchid_create_interface(dchid->ifaces[i]);
+ }
+ }
+}
+
+static void dchid_handle_init(struct dockchannel_hid *dchid, void *data, size_t length)
+{
+ struct dchid_init_hdr *hdr = data;
+ struct dchid_iface *iface;
+ struct dchid_init_block_hdr *blk;
+
+ if (length < sizeof(*hdr))
+ return;
+
+ iface = dchid_get_interface(dchid, hdr->iface, hdr->name);
+ if (!iface)
+ return;
+
+ data += sizeof(*hdr);
+ length -= sizeof(*hdr);
+
+ while (length >= sizeof(*blk)) {
+ blk = data;
+ data += sizeof(*blk);
+ length -= sizeof(*blk);
+
+ if (blk->length > length)
+ break;
+
+ switch (blk->type) {
+ case INIT_HID_DESCRIPTOR:
+ dchid_handle_descriptor(iface, data, blk->length);
+ break;
+
+ case INIT_GPIO_REQUEST: {
+ struct dchid_gpio_request *req = data;
+
+ if (sizeof(*req) > length)
+ break;
+
+ if (iface->gpio_id) {
+ dev_err(dchid->dev,
+ "Cannot request more than one GPIO per interface!\n");
+ break;
+ }
+
+ strlcpy(iface->gpio_name, req->name, MAX_GPIO_NAME);
+ iface->gpio_id = req->id;
+ break;
+ }
+
+ case INIT_TERMINATOR:
+ break;
+
+ case INIT_PRODUCT_NAME: {
+ char *product = data;
+
+ if (product[blk->length - 1] != 0) {
+ dev_warn(dchid->dev, "Unterminated product name for %s\n",
+ iface->name);
+ } else {
+ dev_info(dchid->dev, "Product name for %s: %s\n",
+ iface->name, product);
+ }
+ break;
+ }
+
+ default:
+ dev_warn(dchid->dev, "Unknown init packet %d for %s\n",
+ blk->type, iface->name);
+ break;
+ }
+
+ data += blk->length;
+ length -= blk->length;
+
+ if (blk->type == INIT_TERMINATOR)
+ break;
+ }
+
+ if (hdr->more_packets)
+ return;
+
+ /* We need to enable STM first, since it'll give us the device IDs */
+ if (iface->dchid->id_ready || !strcmp(iface->name, "stm")) {
+ dchid_create_interface(iface);
+ } else {
+ iface->deferred = true;
+ }
+}
+
+static void dchid_handle_gpio(struct dockchannel_hid *dchid, void *data, size_t length)
+{
+ struct dchid_gpio_cmd *cmd = data;
+ struct dchid_iface *iface;
+ u32 retcode = 0xe000f00d; /* Give it a random Apple-style error code */
+ struct dchid_gpio_ack *ack;
+
+ if (length < sizeof(*cmd))
+ return;
+
+ if (cmd->iface >= MAX_INTERFACES || !(iface = dchid->ifaces[cmd->iface])) {
+ dev_err(dchid->dev, "Got GPIO command for bad inteface %d\n", cmd->iface);
+ goto err;
+ }
+
+ if (dchid_request_gpio(iface) < 0)
+ goto err;
+
+ if (!iface->gpio || cmd->gpio != iface->gpio_id) {
+ dev_err(dchid->dev, "Got GPIO command for bad GPIO %s#%d\n",
+ iface->name, cmd->gpio);
+ goto err;
+ }
+
+ dev_info(dchid->dev, "GPIO command: %s#%d: %d\n", iface->name, cmd->gpio, cmd->cmd);
+
+ switch (cmd->cmd) {
+ case 3:
+ /* Pulse. */
+ gpiod_set_value_cansleep(iface->gpio, 1);
+ msleep(10); /* Random guess... */
+ gpiod_set_value_cansleep(iface->gpio, 0);
+ retcode = 0;
+ break;
+ default:
+ dev_err(dchid->dev, "Unknown GPIO command %d\n", cmd->cmd );
+ break;
+ }
+
+err:
+ /* Ack it */
+ ack = kzalloc(sizeof(*ack) + length, GFP_KERNEL);
+ if (!ack)
+ return;
+
+ ack->type = CMD_ACK_GPIO_CMD;
+ ack->retcode = retcode;
+ memcpy(ack->cmd, data, length);
+
+ if (dchid_comm_cmd(dchid, ack, sizeof(*ack) + length) < 0)
+ dev_err(dchid->dev, "Failed to ACK GPIO command\n");
+
+ kfree(ack);
+}
+
+static void dchid_handle_event(struct dockchannel_hid *dchid, void *data, size_t length)
+{
+ u8 *p = data;
+ switch (*p) {
+ case EVENT_INIT:
+ dchid_handle_init(dchid, data, length);
+ break;
+ case EVENT_READY:
+ dchid_handle_ready(dchid, data, length);
+ break;
+ case EVENT_GPIO_CMD:
+ dchid_handle_gpio(dchid, data, length);
+ break;
+ }
+}
+
+static void dchid_handle_report(struct dchid_iface *iface, void *data, size_t length)
+{
+ struct dockchannel_hid *dchid = iface->dchid;
+
+ if (!iface->hid) {
+ dev_warn(dchid->dev, "Report received but %s is not initialized!\n", iface->name);
+ return;
+ }
+
+ if (!iface->open)
+ return;
+
+ hid_input_report(iface->hid, HID_INPUT_REPORT, data, length, 1);
+}
+
+static void dchid_packet_work(struct work_struct *ws)
+{
+ struct dchid_work *work = container_of(ws, struct dchid_work, work);
+ struct dchid_subhdr *shdr = (void *)work->data;
+ struct dockchannel_hid *dchid = work->iface->dchid;
+ int type = FIELD_GET(FLAGS_GROUP, shdr->flags);
+ u8 *payload = work->data + sizeof(*shdr);
+
+ if (shdr->length + sizeof(*shdr) > work->hdr.length) {
+ dev_err(dchid->dev, "Bad sub header length (%d > %zu)\n",
+ shdr->length, work->hdr.length - sizeof(*shdr));
+ return;
+ }
+
+ switch (type) {
+ case GROUP_INPUT:
+ if (work->hdr.iface == IFACE_COMM)
+ dchid_handle_event(dchid, payload, shdr->length);
+ else
+ dchid_handle_report(work->iface, payload, shdr->length);
+ break;
+ default:
+ dev_err(dchid->dev, "Received unknown packet type %d\n", type);
+ break;
+ }
+
+ kfree(work);
+}
+
+static void dchid_handle_ack(struct dchid_iface *iface, struct dchid_hdr *hdr, void *data)
+{
+ struct dchid_subhdr *shdr = (void *)data;
+ u8 *payload = data + sizeof(*shdr);
+
+ if (shdr->length + sizeof(*shdr) > hdr->length) {
+ dev_err(iface->dchid->dev, "Bad sub header length (%d > %ld)\n",
+ shdr->length, hdr->length - sizeof(*shdr));
+ return;
+ }
+ if (shdr->flags != iface->out_flags) {
+ dev_err(iface->dchid->dev,
+ "Received unexpected flags 0x%x on ACK channel (expFected 0x%x)\n",
+ shdr->flags, iface->out_flags);
+ return;
+ }
+
+ if (shdr->length < 1) {
+ dev_err(iface->dchid->dev, "Received length 0 output report ack\n");
+ return;
+ }
+ if (iface->tx_seq != hdr->seq) {
+ dev_err(iface->dchid->dev, "Received ACK with bad seq (expected %d, got %d)\n",
+ iface->tx_seq, hdr->seq);
+ return;
+ }
+ if (iface->out_report != payload[0]) {
+ dev_err(iface->dchid->dev, "Received ACK with bad report (expected %d, got %d\n",
+ iface->out_report, payload[0]);
+ return;
+ }
+
+ if (iface->resp_buf && iface->resp_size)
+ memcpy(iface->resp_buf, payload + 1, min((size_t)shdr->length - 1, iface->resp_size));
+
+ iface->resp_size = shdr->length;
+ iface->out_report = -1;
+ iface->retcode = shdr->retcode;
+ complete(&iface->out_complete);
+}
+
+static void dchid_handle_packet(void *cookie, size_t avail)
+{
+ struct dockchannel_hid *dchid = cookie;
+ struct dchid_hdr hdr;
+ struct dchid_work *work;
+ struct dchid_iface *iface;
+ u32 checksum;
+
+ if (dockchannel_recv(dchid->dc, &hdr, sizeof(hdr)) != sizeof(hdr)) {
+ dev_err(dchid->dev, "Read failed (header)\n");
+ return;
+ }
+
+ if (hdr.hdr_len != sizeof(hdr)) {
+ dev_err(dchid->dev, "Bad header length %d\n", hdr.hdr_len);
+ goto done;
+ }
+
+ if (dockchannel_recv(dchid->dc, dchid->pkt_buf, hdr.length + 4) != (hdr.length + 4)) {
+ dev_err(dchid->dev, "Read failed (body)\n");
+ goto done;
+ }
+
+ checksum = dchid_checksum(&hdr, sizeof(hdr));
+ checksum += dchid_checksum(dchid->pkt_buf, hdr.length + 4);
+
+ if (checksum != 0xffffffff) {
+ dev_err(dchid->dev, "Checksum mismatch (iface %d): 0x%08x != 0xffffffff\n",
+ hdr.iface, checksum);
+ goto done;
+ }
+
+
+ if (hdr.iface >= MAX_INTERFACES) {
+ dev_err(dchid->dev, "Bad iface %d\n", hdr.iface);
+ }
+
+ iface = dchid->ifaces[hdr.iface];
+
+ if (!iface) {
+ dev_err(dchid->dev, "Received packet for uninitialized iface %d\n", hdr.iface);
+ goto done;
+ }
+
+ switch (hdr.channel) {
+ case DCHID_CHANNEL_CMD:
+ dchid_handle_ack(iface, &hdr, dchid->pkt_buf);
+ goto done;
+ case DCHID_CHANNEL_REPORT:
+ break;
+ default:
+ dev_warn(dchid->dev, "Unknown channel 0x%x, treating as report...\n",
+ hdr.channel);
+ break;
+ }
+
+ work = kzalloc(sizeof(*work) + hdr.length, GFP_KERNEL);
+ if (!work)
+ return;
+
+ work->hdr = hdr;
+ work->iface = iface;
+ memcpy(work->data, dchid->pkt_buf, hdr.length);
+ INIT_WORK(&work->work, dchid_packet_work);
+
+ queue_work(iface->wq, &work->work);
+
+done:
+ dockchannel_await(dchid->dc, dchid_handle_packet, dchid, sizeof(struct dchid_hdr));
+}
+
+static int dockchannel_hid_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct dockchannel_hid *dchid;
+ struct device_node *child, *helper;
+ struct platform_device *helper_pdev;
+ struct property *prop;
+ int ret;
+
+ ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
+ if (ret)
+ return ret;
+
+ dchid = devm_kzalloc(dev, sizeof(*dchid), GFP_KERNEL);
+ if (!dchid) {
+ return -ENOMEM;
+ }
+
+ dchid->dev = dev;
+
+ /*
+ * First make sure all the GPIOs are available, in cased we need to defer.
+ * This is necessary because MTP will request them by name later, and by then
+ * it's too late to defer the probe.
+ */
+
+ for_each_child_of_node(dev->of_node, child) {
+ for_each_property_of_node(child, prop) {
+ size_t len = strlen(prop->name);
+ struct gpio_desc *gpio;
+
+ if (len < 12 || strncmp("apple,", prop->name, 6) ||
+ strcmp("-gpios", prop->name + len - 6))
+ continue;
+
+ gpio = fwnode_gpiod_get_index(&child->fwnode, prop->name, 0, GPIOD_ASIS,
+ prop->name);
+ if (IS_ERR_OR_NULL(gpio)) {
+ if (PTR_ERR(gpio) == -EPROBE_DEFER) {
+ of_node_put(child);
+ return -EPROBE_DEFER;
+ }
+ } else {
+ gpiod_put(gpio);
+ }
+ }
+ }
+
+ /*
+ * Make sure we also have the MTP coprocessor available, and
+ * defer probe if the helper hasn't probed yet.
+ */
+ helper = of_parse_phandle(dev->of_node, "apple,helper-cpu", 0);
+ if (!helper) {
+ dev_err(dev, "Missing apple,helper-cpu property");
+ return -EINVAL;
+ }
+
+ helper_pdev = of_find_device_by_node(helper);
+ of_node_put(helper);
+ if (!helper_pdev) {
+ dev_err(dev, "Failed to find helper device");
+ return -EINVAL;
+ }
+
+ dchid->helper_link = device_link_add(dev, &helper_pdev->dev,
+ DL_FLAG_AUTOREMOVE_CONSUMER);
+ put_device(&helper_pdev->dev);
+ if (!dchid->helper_link) {
+ dev_err(dev, "Failed to link to helper device");
+ return -EINVAL;
+ }
+
+ if (dchid->helper_link->supplier->links.status != DL_DEV_DRIVER_BOUND)
+ return -EPROBE_DEFER;
+
+ /* Now it is safe to begin initializing */
+ dchid->dc = dockchannel_init(pdev);
+ if (IS_ERR_OR_NULL(dchid->dc)) {
+ return PTR_ERR(dchid->dc);
+ }
+ dchid->new_iface_wq = alloc_workqueue("dchid-new", WQ_MEM_RECLAIM, 0);
+ if (!dchid->new_iface_wq)
+ return -ENOMEM;
+
+ dchid->comm = dchid_get_interface(dchid, IFACE_COMM, "comm");
+ if (!dchid->comm) {
+ dev_err(dchid->dev, "Failed to initialize comm interface");
+ return -EIO;
+ }
+
+ dev_info(dchid->dev, "Initialized, awaiting packets\n");
+ dockchannel_await(dchid->dc, dchid_handle_packet, dchid, sizeof(struct dchid_hdr));
+
+ return 0;
+}
+
+static int dockchannel_hid_remove(struct platform_device *pdev)
+{
+ BUG_ON(1);
+ return 0;
+}
+
+static const struct of_device_id dockchannel_hid_of_match[] = {
+ { .compatible = "apple,dockchannel-hid" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, dockchannel_hid_of_match);
+MODULE_FIRMWARE("apple/tpmtfw-*.bin");
+
+static struct platform_driver dockchannel_hid_driver = {
+ .driver = {
+ .name = "dockchannel-hid",
+ .of_match_table = dockchannel_hid_of_match,
+ },
+ .probe = dockchannel_hid_probe,
+ .remove = dockchannel_hid_remove,
+};
+module_platform_driver(dockchannel_hid_driver);
+
+MODULE_DESCRIPTION("Apple DockChannel HID transport driver");
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
+MODULE_LICENSE("Dual MIT/GPL");
diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c
index 3ca4597..a58022f 100644
--- a/drivers/hid/hid-apple.c
+++ b/drivers/hid/hid-apple.c
@@ -263,6 +263,50 @@ static const struct apple_key_translation apple_fn_keys[] = {
{ }
};
+static const struct apple_key_translation apple_fn_keys_spi[] = {
+ { KEY_BACKSPACE, KEY_DELETE },
+ { KEY_ENTER, KEY_INSERT },
+ { KEY_F1, KEY_BRIGHTNESSDOWN, APPLE_FLAG_FKEY },
+ { KEY_F2, KEY_BRIGHTNESSUP, APPLE_FLAG_FKEY },
+ { KEY_F3, KEY_SCALE, APPLE_FLAG_FKEY },
+ { KEY_F4, KEY_SEARCH, APPLE_FLAG_FKEY },
+ { KEY_F5, KEY_RECORD, APPLE_FLAG_FKEY },
+ { KEY_F6, KEY_SLEEP, APPLE_FLAG_FKEY },
+ { KEY_F7, KEY_PREVIOUSSONG, APPLE_FLAG_FKEY },
+ { KEY_F8, KEY_PLAYPAUSE, APPLE_FLAG_FKEY },
+ { KEY_F9, KEY_NEXTSONG, APPLE_FLAG_FKEY },
+ { KEY_F10, KEY_MUTE, APPLE_FLAG_FKEY },
+ { KEY_F11, KEY_VOLUMEDOWN, APPLE_FLAG_FKEY },
+ { KEY_F12, KEY_VOLUMEUP, APPLE_FLAG_FKEY },
+ { KEY_UP, KEY_PAGEUP },
+ { KEY_DOWN, KEY_PAGEDOWN },
+ { KEY_LEFT, KEY_HOME },
+ { KEY_RIGHT, KEY_END },
+ { }
+};
+
+static const struct apple_key_translation apple_fn_keys_mbp13[] = {
+ { KEY_BACKSPACE, KEY_DELETE },
+ { KEY_ENTER, KEY_INSERT },
+ { KEY_UP, KEY_PAGEUP },
+ { KEY_DOWN, KEY_PAGEDOWN },
+ { KEY_LEFT, KEY_HOME },
+ { KEY_RIGHT, KEY_END },
+ { KEY_1, KEY_F1 },
+ { KEY_2, KEY_F2 },
+ { KEY_3, KEY_F3 },
+ { KEY_4, KEY_F4 },
+ { KEY_5, KEY_F5 },
+ { KEY_6, KEY_F6 },
+ { KEY_7, KEY_F7 },
+ { KEY_8, KEY_F8 },
+ { KEY_9, KEY_F9 },
+ { KEY_0, KEY_F10 },
+ { KEY_MINUS, KEY_F11 },
+ { KEY_EQUAL, KEY_F12 },
+ { }
+};
+
static const struct apple_key_translation powerbook_fn_keys[] = {
{ KEY_BACKSPACE, KEY_DELETE },
{ KEY_F1, KEY_BRIGHTNESSDOWN, APPLE_FLAG_FKEY },
@@ -474,6 +518,16 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input,
else if (hid->product >= USB_DEVICE_ID_APPLE_WELLSPRING4_ANSI &&
hid->product <= USB_DEVICE_ID_APPLE_WELLSPRING4A_JIS)
table = macbookair_fn_keys;
+ else if (hid->bus == BUS_HOST || hid->bus == BUS_SPI)
+ switch (hid->product) {
+ case SPI_DEVICE_ID_APPLE_MACBOOK_PRO13_2020:
+ case HOST_DEVICE_ID_APPLE_MACBOOK_PRO13_2022:
+ table = apple_fn_keys_mbp13;
+ break;
+ default:
+ table = apple_fn_keys_spi;
+ break;
+ }
else if (hid->product < 0x21d || hid->product >= 0x300)
table = powerbook_fn_keys;
else
@@ -653,6 +707,8 @@ static void apple_setup_input(struct input_dev *input)
/* Enable all needed keys */
apple_setup_key_translation(input, apple_fn_keys);
+ apple_setup_key_translation(input, apple_fn_keys_spi);
+ apple_setup_key_translation(input, apple_fn_keys_mbp13);
apple_setup_key_translation(input, powerbook_fn_keys);
apple_setup_key_translation(input, powerbook_numlock_keys);
apple_setup_key_translation(input, apple_iso_keyboard);
@@ -826,6 +882,10 @@ static int apple_probe(struct hid_device *hdev,
struct apple_sc *asc;
int ret;
+ if ((id->bus == BUS_SPI || id->bus == BUS_HOST) && id->vendor == SPI_VENDOR_ID_APPLE &&
+ hdev->type != HID_TYPE_SPI_KEYBOARD)
+ return -ENODEV;
+
asc = devm_kzalloc(&hdev->dev, sizeof(*asc), GFP_KERNEL);
if (asc == NULL) {
hid_err(hdev, "can't alloc apple descriptor\n");
@@ -1070,6 +1130,10 @@ static const struct hid_device_id apple_devices[] = {
.driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_RDESC_BATTERY },
{ HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2021),
.driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+ { HID_SPI_DEVICE(SPI_VENDOR_ID_APPLE, HID_ANY_ID),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+ { HID_DEVICE(BUS_HOST, HID_GROUP_ANY, HOST_VENDOR_ID_APPLE, HID_ANY_ID),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
{ }
};
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index 8992e3c..51799b3 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -436,7 +436,10 @@ static int hid_parser_global(struct hid_parser *parser, struct hid_item *item)
case HID_GLOBAL_ITEM_TAG_REPORT_SIZE:
parser->global.report_size = item_udata(item);
- if (parser->global.report_size > 256) {
+ /* Arbitrary maximum. Some Apple devices have 16384 here.
+ * This * HID_MAX_USAGES must fit in a signed integer.
+ */
+ if (parser->global.report_size > 16384) {
hid_err(parser->device, "invalid report_size %d\n",
parser->global.report_size);
return -1;
@@ -2242,6 +2245,12 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask)
case BUS_I2C:
bus = "I2C";
break;
+ case BUS_SPI:
+ bus = "SPI";
+ break;
+ case BUS_HOST:
+ bus = "HOST";
+ break;
case BUS_VIRTUAL:
bus = "VIRTUAL";
break;
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index f7973cc..2be2d5a 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -89,6 +89,8 @@
#define USB_VENDOR_ID_APPLE 0x05ac
#define BT_VENDOR_ID_APPLE 0x004c
+#define SPI_VENDOR_ID_APPLE 0x05ac
+#define HOST_VENDOR_ID_APPLE 0x05ac
#define USB_DEVICE_ID_APPLE_MIGHTYMOUSE 0x0304
#define USB_DEVICE_ID_APPLE_MAGICMOUSE 0x030d
#define USB_DEVICE_ID_APPLE_MAGICMOUSE2 0x0269
@@ -187,6 +189,12 @@
#define USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2021 0x029f
#define USB_DEVICE_ID_APPLE_TOUCHBAR_BACKLIGHT 0x8102
#define USB_DEVICE_ID_APPLE_TOUCHBAR_DISPLAY 0x8302
+#define SPI_DEVICE_ID_APPLE_MACBOOK_AIR_2020 0x0281
+#define SPI_DEVICE_ID_APPLE_MACBOOK_PRO13_2020 0x0341
+#define SPI_DEVICE_ID_APPLE_MACBOOK_PRO14_2021 0x0342
+#define SPI_DEVICE_ID_APPLE_MACBOOK_PRO16_2021 0x0343
+#define HOST_DEVICE_ID_APPLE_MACBOOK_AIR13_2022 0x0351
+#define HOST_DEVICE_ID_APPLE_MACBOOK_PRO13_2022 0x0354
#define USB_VENDOR_ID_ASUS 0x0486
#define USB_DEVICE_ID_ASUS_T91MT 0x0185
diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c
index c9c968d..54206fa 100644
--- a/drivers/hid/hid-magicmouse.c
+++ b/drivers/hid/hid-magicmouse.c
@@ -59,8 +59,13 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie
#define MOUSE_REPORT_ID 0x29
#define MOUSE2_REPORT_ID 0x12
#define DOUBLE_REPORT_ID 0xf7
+#define SPI_REPORT_ID 0x02
+#define SPI_RESET_REPORT_ID 0x60
+#define MTP_REPORT_ID 0x75
#define USB_BATTERY_TIMEOUT_MS 60000
+#define MAX_CONTACTS 16
+
/* These definitions are not precise, but they're close enough. (Bits
* 0x03 seem to indicate the aspect ratio of the touch, bits 0x70 seem
* to be some kind of bit mask -- 0x20 may be a near-field reading,
@@ -111,6 +116,25 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie
#define TRACKPAD2_RES_Y \
((TRACKPAD2_MAX_Y - TRACKPAD2_MIN_Y) / (TRACKPAD2_DIMENSION_Y / 100))
+#define J314_TP_DIMENSION_X (float)13000
+#define J314_TP_MIN_X -5900
+#define J314_TP_MAX_X 6500
+#define J314_TP_RES_X \
+ ((J314_TP_MAX_X - J314_TP_MIN_X) / (J314_TP_DIMENSION_X / 100))
+#define J314_TP_DIMENSION_Y (float)8100
+#define J314_TP_MIN_Y -200
+#define J314_TP_MAX_Y 7400
+#define J314_TP_RES_Y \
+ ((J314_TP_MAX_Y - J314_TP_MIN_Y) / (J314_TP_DIMENSION_Y / 100))
+
+#define J314_TP_MAX_FINGER_ORIENTATION 16384
+
+struct magicmouse_input_ops {
+ int (*raw_event)(struct hid_device *hdev,
+ struct hid_report *report, u8 *data, int size);
+ int (*setup_input)(struct input_dev *input, struct hid_device *hdev);
+};
+
/**
* struct magicmouse_sc - Tracks Magic Mouse-specific data.
* @input: Input device through which we report events.
@@ -129,9 +153,8 @@ struct magicmouse_sc {
int scroll_accel;
unsigned long scroll_jiffies;
+ struct input_mt_pos pos[MAX_CONTACTS];
struct {
- short x;
- short y;
short scroll_x;
short scroll_y;
short scroll_x_hr;
@@ -139,14 +162,106 @@ struct magicmouse_sc {
u8 size;
bool scroll_x_active;
bool scroll_y_active;
- } touches[16];
- int tracking_ids[16];
+ } touches[MAX_CONTACTS];
+ int tracking_ids[MAX_CONTACTS];
struct hid_device *hdev;
struct delayed_work work;
struct timer_list battery_timer;
+ struct magicmouse_input_ops input_ops;
};
+static int magicmouse_enable_multitouch(struct hid_device *hdev)
+{
+ const u8 *feature;
+ const u8 feature_mt[] = { 0xD7, 0x01 };
+ const u8 feature_mt_mouse2[] = { 0xF1, 0x02, 0x01 };
+ const u8 feature_mt_trackpad2_usb[] = { 0x02, 0x01 };
+ const u8 feature_mt_trackpad2_bt[] = { 0xF1, 0x02, 0x01 };
+ u8 *buf;
+ int ret;
+ int feature_size;
+
+ if (hdev->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
+ if (hdev->vendor == BT_VENDOR_ID_APPLE) {
+ feature_size = sizeof(feature_mt_trackpad2_bt);
+ feature = feature_mt_trackpad2_bt;
+ } else { /* USB_VENDOR_ID_APPLE */
+ feature_size = sizeof(feature_mt_trackpad2_usb);
+ feature = feature_mt_trackpad2_usb;
+ }
+ } else if (hdev->vendor == SPI_VENDOR_ID_APPLE) {
+ feature_size = sizeof(feature_mt_trackpad2_usb);
+ feature = feature_mt_trackpad2_usb;
+ } else if (hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
+ feature_size = sizeof(feature_mt_mouse2);
+ feature = feature_mt_mouse2;
+ } else {
+ feature_size = sizeof(feature_mt);
+ feature = feature_mt;
+ }
+
+ buf = kmemdup(feature, feature_size, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ ret = hid_hw_raw_request(hdev, buf[0], buf, feature_size,
+ HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+ kfree(buf);
+ return ret;
+}
+
+static void magicmouse_enable_mt_work(struct work_struct *work)
+{
+ struct magicmouse_sc *msc =
+ container_of(work, struct magicmouse_sc, work.work);
+ int ret;
+
+ ret = magicmouse_enable_multitouch(msc->hdev);
+ if (ret < 0)
+ hid_err(msc->hdev, "unable to request touch data (%d)\n", ret);
+}
+
+static int magicmouse_open(struct input_dev *dev)
+{
+ struct hid_device *hdev = input_get_drvdata(dev);
+ struct magicmouse_sc *msc = hid_get_drvdata(hdev);
+ int ret;
+
+ ret = hid_hw_open(hdev);
+ if (ret)
+ return ret;
+
+ /*
+ * Some devices repond with 'invalid report id' when feature
+ * report switching it into multitouch mode is sent to it.
+ *
+ * This results in -EIO from the _raw low-level transport callback,
+ * but there seems to be no other way of switching the mode.
+ * Thus the super-ugly hacky success check below.
+ */
+ ret = magicmouse_enable_multitouch(hdev);
+ if (ret == -EIO && hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
+ schedule_delayed_work(&msc->work, msecs_to_jiffies(500));
+ return 0;
+ }
+ if (ret < 0)
+ hid_err(hdev, "unable to request touch data (%d)\n", ret);
+
+ /*
+ * MT enable is usually not required after the first time, so don't
+ * consider it fatal.
+ */
+ return 0;
+}
+
+static void magicmouse_close(struct input_dev *dev)
+{
+ struct hid_device *hdev = input_get_drvdata(dev);
+
+ hid_hw_close(hdev);
+}
+
static int magicmouse_firm_touch(struct magicmouse_sc *msc)
{
int touch = -1;
@@ -188,7 +303,7 @@ static void magicmouse_emit_buttons(struct magicmouse_sc *msc, int state)
} else if (last_state != 0) {
state = last_state;
} else if ((id = magicmouse_firm_touch(msc)) >= 0) {
- int x = msc->touches[id].x;
+ int x = msc->pos[id].x;
if (x < middle_button_start)
state = 1;
else if (x > middle_button_stop)
@@ -249,8 +364,8 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda
/* Store tracking ID and other fields. */
msc->tracking_ids[raw_id] = id;
- msc->touches[id].x = x;
- msc->touches[id].y = y;
+ msc->pos[id].x = x;
+ msc->pos[id].y = y;
msc->touches[id].size = size;
/* If requested, emulate a scroll wheel by detecting small
@@ -374,6 +489,14 @@ static int magicmouse_raw_event(struct hid_device *hdev,
struct hid_report *report, u8 *data, int size)
{
struct magicmouse_sc *msc = hid_get_drvdata(hdev);
+
+ return msc->input_ops.raw_event(hdev, report, data, size);
+}
+
+static int magicmouse_raw_event_usb(struct hid_device *hdev,
+ struct hid_report *report, u8 *data, int size)
+{
+ struct magicmouse_sc *msc = hid_get_drvdata(hdev);
struct input_dev *input = msc->input;
int x = 0, y = 0, ii, clicks = 0, npoints;
@@ -502,6 +625,182 @@ static int magicmouse_raw_event(struct hid_device *hdev,
return 1;
}
+/**
+ * struct tp_finger - single trackpad finger structure, le16-aligned
+ *
+ * @unknown1: unknown
+ * @unknown2: unknown
+ * @abs_x: absolute x coordinate
+ * @abs_y: absolute y coordinate
+ * @rel_x: relative x coordinate
+ * @rel_y: relative y coordinate
+ * @tool_major: tool area, major axis
+ * @tool_minor: tool area, minor axis
+ * @orientation: 16384 when point, else 15 bit angle
+ * @touch_major: touch area, major axis
+ * @touch_minor: touch area, minor axis
+ * @unused: zeros
+ * @pressure: pressure on forcetouch touchpad
+ * @multi: one finger: varies, more fingers: constant
+ * @crc16: on last finger: crc over the whole message struct
+ * (i.e. message header + this struct) minus the last
+ * @crc16 field; unknown on all other fingers.
+ */
+struct tp_finger {
+ __le16 unknown1;
+ __le16 unknown2;
+ __le16 abs_x;
+ __le16 abs_y;
+ __le16 rel_x;
+ __le16 rel_y;
+ __le16 tool_major;
+ __le16 tool_minor;
+ __le16 orientation;
+ __le16 touch_major;
+ __le16 touch_minor;
+ __le16 unused[2];
+ __le16 pressure;
+ __le16 multi;
+} __attribute__((packed, aligned(2)));
+
+/**
+ * vendor trackpad report
+ *
+ * @num_fingers: the number of fingers being reported in @fingers
+ * @buttons: same as HID buttons
+ */
+struct tp_header {
+ // HID vendor part, up to 1751 bytes
+ u8 unknown[22];
+ u8 num_fingers;
+ u8 buttons;
+ u8 unknown3[14];
+};
+
+/**
+ * standard HID mouse report
+ *
+ * @report_id: reportid
+ * @buttons: HID Usage Buttons 3 1-bit reports
+ */
+struct tp_mouse_report {
+ // HID mouse report
+ u8 report_id;
+ u8 buttons;
+ u8 rel_x;
+ u8 rel_y;
+ u8 padding[4];
+};
+
+static inline int le16_to_int(__le16 x)
+{
+ return (signed short)le16_to_cpu(x);
+}
+
+static void report_finger_data(struct input_dev *input, int slot,
+ const struct input_mt_pos *pos,
+ const struct tp_finger *f)
+{
+ input_mt_slot(input, slot);
+ input_mt_report_slot_state(input, MT_TOOL_FINGER, true);
+
+ input_report_abs(input, ABS_MT_TOUCH_MAJOR,
+ le16_to_int(f->touch_major) << 1);
+ input_report_abs(input, ABS_MT_TOUCH_MINOR,
+ le16_to_int(f->touch_minor) << 1);
+ input_report_abs(input, ABS_MT_WIDTH_MAJOR,
+ le16_to_int(f->tool_major) << 1);
+ input_report_abs(input, ABS_MT_WIDTH_MINOR,
+ le16_to_int(f->tool_minor) << 1);
+ input_report_abs(input, ABS_MT_ORIENTATION,
+ J314_TP_MAX_FINGER_ORIENTATION - le16_to_int(f->orientation));
+ input_report_abs(input, ABS_MT_PRESSURE, le16_to_int(f->pressure));
+ input_report_abs(input, ABS_MT_POSITION_X, pos->x);
+ input_report_abs(input, ABS_MT_POSITION_Y, pos->y);
+}
+
+static int magicmouse_raw_event_mtp(struct hid_device *hdev,
+ struct hid_report *report, u8 *data, int size)
+{
+ struct magicmouse_sc *msc = hid_get_drvdata(hdev);
+ struct input_dev *input = msc->input;
+ struct tp_header *tp_hdr;
+ struct tp_finger *f;
+ int i, n;
+ u32 npoints;
+ const size_t hdr_sz = sizeof(struct tp_header);
+ const size_t touch_sz = sizeof(struct tp_finger);
+ u8 map_contacs[MAX_CONTACTS];
+
+ // hid_warn(hdev, "%s\n", __func__);
+ // print_hex_dump_debug("appleft ev: ", DUMP_PREFIX_OFFSET, 16, 1, data,
+ // size, false);
+
+ /* Expect 46 bytes of prefix, and N * 30 bytes of touch data. */
+ if (size < hdr_sz || ((size - hdr_sz) % touch_sz) != 0)
+ return 0;
+
+ tp_hdr = (struct tp_header *)data;
+
+ npoints = (size - hdr_sz) / touch_sz;
+ if (npoints < tp_hdr->num_fingers || npoints > MAX_CONTACTS) {
+ hid_warn(hdev,
+ "unexpected number of touches (%u) for "
+ "report\n",
+ npoints);
+ return 0;
+ }
+
+ n = 0;
+ for (i = 0; i < tp_hdr->num_fingers; i++) {
+ f = (struct tp_finger *)(data + hdr_sz + i * touch_sz);
+ if (le16_to_int(f->touch_major) == 0)
+ continue;
+
+ hid_dbg(hdev, "ev x:%04x y:%04x\n", le16_to_int(f->abs_x),
+ le16_to_int(f->abs_y));
+ msc->pos[n].x = le16_to_int(f->abs_x);
+ msc->pos[n].y = -le16_to_int(f->abs_y);
+ map_contacs[n] = i;
+ n++;
+ }
+
+ input_mt_assign_slots(input, msc->tracking_ids, msc->pos, n, 0);
+
+ for (i = 0; i < n; i++) {
+ int idx = map_contacs[i];
+ f = (struct tp_finger *)(data + hdr_sz + idx * touch_sz);
+ report_finger_data(input, msc->tracking_ids[i], &msc->pos[i], f);
+ }
+
+ input_mt_sync_frame(input);
+ input_report_key(input, BTN_MOUSE, tp_hdr->buttons & 1);
+
+ input_sync(input);
+ return 1;
+}
+
+static int magicmouse_raw_event_spi(struct hid_device *hdev,
+ struct hid_report *report, u8 *data, int size)
+{
+ struct magicmouse_sc *msc = hid_get_drvdata(hdev);
+ const size_t hdr_sz = sizeof(struct tp_mouse_report);
+
+ if (!size)
+ return 0;
+
+ if (data[0] == SPI_RESET_REPORT_ID) {
+ hid_info(hdev, "Touch controller was reset, re-enabling touch mode\n");
+ schedule_delayed_work(&msc->work, msecs_to_jiffies(10));
+ return 1;
+ }
+
+ if (data[0] != TRACKPAD2_USB_REPORT_ID || size < hdr_sz)
+ return 0;
+
+ return magicmouse_raw_event_mtp(hdev, report, data + hdr_sz, size - hdr_sz);
+}
+
static int magicmouse_event(struct hid_device *hdev, struct hid_field *field,
struct hid_usage *usage, __s32 value)
{
@@ -519,7 +818,17 @@ static int magicmouse_event(struct hid_device *hdev, struct hid_field *field,
return 0;
}
-static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hdev)
+
+static int magicmouse_setup_input(struct input_dev *input,
+ struct hid_device *hdev)
+{
+ struct magicmouse_sc *msc = hid_get_drvdata(hdev);
+
+ return msc->input_ops.setup_input(input, hdev);
+}
+
+static int magicmouse_setup_input_usb(struct input_dev *input,
+ struct hid_device *hdev)
{
int error;
int mt_flags = 0;
@@ -592,7 +901,7 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
__set_bit(EV_ABS, input->evbit);
- error = input_mt_init_slots(input, 16, mt_flags);
+ error = input_mt_init_slots(input, MAX_CONTACTS, mt_flags);
if (error)
return error;
input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255 << 2,
@@ -668,6 +977,105 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
*/
__clear_bit(EV_REP, input->evbit);
+ /*
+ * This isn't strictly speaking needed for USB, but enabling MT on
+ * device open is probably more robust than only doing it once on probe
+ * even if USB devices are not known to suffer from the SPI reset issue.
+ */
+ input->open = magicmouse_open;
+ input->close = magicmouse_close;
+ return 0;
+}
+
+static int magicmouse_setup_input_mtp(struct input_dev *input,
+ struct hid_device *hdev)
+{
+ int error;
+ int mt_flags = 0;
+
+ __set_bit(INPUT_PROP_BUTTONPAD, input->propbit);
+ __clear_bit(BTN_0, input->keybit);
+ __clear_bit(BTN_RIGHT, input->keybit);
+ __clear_bit(BTN_MIDDLE, input->keybit);
+ __clear_bit(EV_REL, input->evbit);
+ __clear_bit(REL_X, input->relbit);
+ __clear_bit(REL_Y, input->relbit);
+
+ mt_flags = INPUT_MT_POINTER | INPUT_MT_DROP_UNUSED | INPUT_MT_TRACK;
+
+ /* finger touch area */
+ input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 5000, 0, 0);
+ input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0, 5000, 0, 0);
+
+ /* finger approach area */
+ input_set_abs_params(input, ABS_MT_WIDTH_MAJOR, 0, 5000, 0, 0);
+ input_set_abs_params(input, ABS_MT_WIDTH_MINOR, 0, 5000, 0, 0);
+
+ /* Note: Touch Y position from the device is inverted relative
+ * to how pointer motion is reported (and relative to how USB
+ * HID recommends the coordinates work). This driver keeps
+ * the origin at the same position, and just uses the additive
+ * inverse of the reported Y.
+ */
+
+ input_set_abs_params(input, ABS_MT_PRESSURE, 0, 6000, 0, 0);
+
+ /*
+ * This makes libinput recognize this as a PressurePad and
+ * stop trying to use pressure for touch size. Pressure unit
+ * seems to be ~grams on these touchpads.
+ */
+ input_abs_set_res(input, ABS_MT_PRESSURE, 1);
+
+ /* finger orientation */
+ input_set_abs_params(input, ABS_MT_ORIENTATION, -J314_TP_MAX_FINGER_ORIENTATION,
+ J314_TP_MAX_FINGER_ORIENTATION, 0, 0);
+
+ /* finger position */
+ input_set_abs_params(input, ABS_MT_POSITION_X, J314_TP_MIN_X, J314_TP_MAX_X,
+ 0, 0);
+ /* Y axis is inverted */
+ input_set_abs_params(input, ABS_MT_POSITION_Y, -J314_TP_MAX_Y, -J314_TP_MIN_Y,
+ 0, 0);
+
+ /* X/Y resolution */
+ input_abs_set_res(input, ABS_MT_POSITION_X, J314_TP_RES_X);
+ input_abs_set_res(input, ABS_MT_POSITION_Y, J314_TP_RES_Y);
+
+ input_set_events_per_packet(input, 60);
+
+ /* touchpad button */
+ input_set_capability(input, EV_KEY, BTN_MOUSE);
+
+ /*
+ * hid-input may mark device as using autorepeat, but the trackpad does
+ * not actually want it.
+ */
+ __clear_bit(EV_REP, input->evbit);
+
+ error = input_mt_init_slots(input, MAX_CONTACTS, mt_flags);
+ if (error)
+ return error;
+
+ return 0;
+}
+
+static int magicmouse_setup_input_spi(struct input_dev *input,
+ struct hid_device *hdev)
+{
+ int ret = magicmouse_setup_input_mtp(input, hdev);
+ if (ret)
+ return ret;
+
+ /*
+ * Override the default input->open function to send the MT
+ * enable every time the device is opened. This ensures it works
+ * even if we missed a reset event due to the device being closed.
+ * input->close is overridden for symmetry.
+ */
+ input->open = magicmouse_open;
+ input->close = magicmouse_close;
+
return 0;
}
@@ -707,54 +1115,6 @@ static int magicmouse_input_configured(struct hid_device *hdev,
return 0;
}
-static int magicmouse_enable_multitouch(struct hid_device *hdev)
-{
- const u8 *feature;
- const u8 feature_mt[] = { 0xD7, 0x01 };
- const u8 feature_mt_mouse2[] = { 0xF1, 0x02, 0x01 };
- const u8 feature_mt_trackpad2_usb[] = { 0x02, 0x01 };
- const u8 feature_mt_trackpad2_bt[] = { 0xF1, 0x02, 0x01 };
- u8 *buf;
- int ret;
- int feature_size;
-
- if (hdev->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
- if (hdev->vendor == BT_VENDOR_ID_APPLE) {
- feature_size = sizeof(feature_mt_trackpad2_bt);
- feature = feature_mt_trackpad2_bt;
- } else { /* USB_VENDOR_ID_APPLE */
- feature_size = sizeof(feature_mt_trackpad2_usb);
- feature = feature_mt_trackpad2_usb;
- }
- } else if (hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
- feature_size = sizeof(feature_mt_mouse2);
- feature = feature_mt_mouse2;
- } else {
- feature_size = sizeof(feature_mt);
- feature = feature_mt;
- }
-
- buf = kmemdup(feature, feature_size, GFP_KERNEL);
- if (!buf)
- return -ENOMEM;
-
- ret = hid_hw_raw_request(hdev, buf[0], buf, feature_size,
- HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
- kfree(buf);
- return ret;
-}
-
-static void magicmouse_enable_mt_work(struct work_struct *work)
-{
- struct magicmouse_sc *msc =
- container_of(work, struct magicmouse_sc, work.work);
- int ret;
-
- ret = magicmouse_enable_multitouch(msc->hdev);
- if (ret < 0)
- hid_err(msc->hdev, "unable to request touch data (%d)\n", ret);
-}
-
static int magicmouse_fetch_battery(struct hid_device *hdev)
{
#ifdef CONFIG_HID_BATTERY_STRENGTH
@@ -800,12 +1160,30 @@ static int magicmouse_probe(struct hid_device *hdev,
struct hid_report *report;
int ret;
+ if ((id->bus == BUS_SPI || id->bus == BUS_HOST) && id->vendor == SPI_VENDOR_ID_APPLE &&
+ hdev->type != HID_TYPE_SPI_MOUSE)
+ return -ENODEV;
+
msc = devm_kzalloc(&hdev->dev, sizeof(*msc), GFP_KERNEL);
if (msc == NULL) {
hid_err(hdev, "can't alloc magicmouse descriptor\n");
return -ENOMEM;
}
+ // internal trackpad use a data format use input ops to avoid
+ // conflicts with the report ID.
+ if (id->bus == BUS_HOST) {
+ msc->input_ops.raw_event = magicmouse_raw_event_mtp;
+ msc->input_ops.setup_input = magicmouse_setup_input_mtp;
+ } else if (id->bus == BUS_SPI) {
+ msc->input_ops.raw_event = magicmouse_raw_event_spi;
+ msc->input_ops.setup_input = magicmouse_setup_input_spi;
+
+ } else {
+ msc->input_ops.raw_event = magicmouse_raw_event_usb;
+ msc->input_ops.setup_input = magicmouse_setup_input_usb;
+ }
+
msc->scroll_accel = SCROLL_ACCEL_DEFAULT;
msc->hdev = hdev;
INIT_DEFERRABLE_WORK(&msc->work, magicmouse_enable_mt_work);
@@ -854,6 +1232,10 @@ static int magicmouse_probe(struct hid_device *hdev,
else /* USB_VENDOR_ID_APPLE */
report = hid_register_report(hdev, HID_INPUT_REPORT,
TRACKPAD2_USB_REPORT_ID, 0);
+ } else if (id->bus == BUS_SPI) {
+ report = hid_register_report(hdev, HID_INPUT_REPORT, SPI_REPORT_ID, 0);
+ } else if (id->bus == BUS_HOST) {
+ report = hid_register_report(hdev, HID_INPUT_REPORT, MTP_REPORT_ID, 0);
} else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
report = hid_register_report(hdev, HID_INPUT_REPORT,
TRACKPAD_REPORT_ID, 0);
@@ -868,21 +1250,14 @@ static int magicmouse_probe(struct hid_device *hdev,
}
report->size = 6;
- /*
- * Some devices repond with 'invalid report id' when feature
- * report switching it into multitouch mode is sent to it.
- *
- * This results in -EIO from the _raw low-level transport callback,
- * but there seems to be no other way of switching the mode.
- * Thus the super-ugly hacky success check below.
- */
- ret = magicmouse_enable_multitouch(hdev);
- if (ret != -EIO && ret < 0) {
- hid_err(hdev, "unable to request touch data (%d)\n", ret);
- goto err_stop_hw;
- }
- if (ret == -EIO && id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
- schedule_delayed_work(&msc->work, msecs_to_jiffies(500));
+ /* MTP devices do not need the MT enable, this is handled by the MTP driver */
+ if (id->bus == BUS_HOST)
+ return 0;
+
+ /* SPI devices need to watch for reset events to re-send the MT enable */
+ if (id->bus == BUS_SPI) {
+ report = hid_register_report(hdev, HID_INPUT_REPORT, SPI_RESET_REPORT_ID, 0);
+ report->size = 2;
}
return 0;
@@ -948,10 +1323,24 @@ static const struct hid_device_id magic_mice[] = {
USB_DEVICE_ID_APPLE_MAGICTRACKPAD2), .driver_data = 0 },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE,
USB_DEVICE_ID_APPLE_MAGICTRACKPAD2), .driver_data = 0 },
+ { HID_SPI_DEVICE(SPI_VENDOR_ID_APPLE, HID_ANY_ID),
+ .driver_data = 0 },
+ { HID_DEVICE(BUS_HOST, HID_GROUP_ANY, HOST_VENDOR_ID_APPLE,
+ HID_ANY_ID), .driver_data = 0 },
{ }
};
MODULE_DEVICE_TABLE(hid, magic_mice);
+#ifdef CONFIG_PM
+static int magicmouse_reset_resume(struct hid_device *hdev)
+{
+ if (hdev->bus == BUS_SPI)
+ return magicmouse_enable_multitouch(hdev);
+
+ return 0;
+}
+#endif
+
static struct hid_driver magicmouse_driver = {
.name = "magicmouse",
.id_table = magic_mice,
@@ -962,6 +1351,10 @@ static struct hid_driver magicmouse_driver = {
.event = magicmouse_event,
.input_mapping = magicmouse_input_mapping,
.input_configured = magicmouse_input_configured,
+#ifdef CONFIG_PM
+ .reset_resume = magicmouse_reset_resume,
+#endif
+
};
module_hid_driver(magicmouse_driver);
diff --git a/drivers/hid/spi-hid/Kconfig b/drivers/hid/spi-hid/Kconfig
new file mode 100644
index 0000000..8e37f0f
--- /dev/null
+++ b/drivers/hid/spi-hid/Kconfig
@@ -0,0 +1,26 @@
+# SPDX-License-Identifier: GPL-2.0-only
+menu "SPI HID support"
+ depends on SPI
+
+config SPI_HID_APPLE_OF
+ tristate "HID over SPI transport layer for Apple Silicon SoCs"
+ default ARCH_APPLE
+ depends on SPI && INPUT && OF
+ help
+ Say Y here if you use Apple Silicon based laptop. The keyboard and
+ touchpad are HID based devices connected via SPI.
+
+ If unsure, say N.
+
+ This support is also available as a module. If so, the module
+ will be called spi-hid-apple-of. It will also build/depend on the
+ module spi-hid-apple.
+
+endmenu
+
+config SPI_HID_APPLE_CORE
+ tristate
+ default y if SPI_HID_APPLE_OF=y
+ default m if SPI_HID_APPLE_OF=m
+ select HID
+ select CRC16
diff --git a/drivers/hid/spi-hid/Makefile b/drivers/hid/spi-hid/Makefile
new file mode 100644
index 0000000..f276ee1
--- /dev/null
+++ b/drivers/hid/spi-hid/Makefile
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for SPI HID tarnsport drivers
+#
+
+obj-$(CONFIG_SPI_HID_APPLE_CORE) += spi-hid-apple.o
+
+spi-hid-apple-objs = spi-hid-apple-core.o
+
+obj-$(CONFIG_SPI_HID_APPLE_OF) += spi-hid-apple-of.o
diff --git a/drivers/hid/spi-hid/spi-hid-apple-core.c b/drivers/hid/spi-hid/spi-hid-apple-core.c
new file mode 100644
index 0000000..787dcd9
--- /dev/null
+++ b/drivers/hid/spi-hid/spi-hid-apple-core.c
@@ -0,0 +1,1129 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0
+ *
+ * Apple SPI HID transport driver
+ *
+ * Copyright (C) The Asahi Linux Contributors
+ *
+ * Based on: drivers/input/applespi.c
+ *
+ * MacBook (Pro) SPI keyboard and touchpad driver
+ *
+ * Copyright (c) 2015-2018 Federico Lorenzi
+ * Copyright (c) 2017-2018 Ronald Tschalär
+ *
+ */
+
+//#define DEBUG 2
+
+#include <asm/unaligned.h>
+#include <linux/crc16.h>
+#include <linux/delay.h>
+#include <linux/device/driver.h>
+#include <linux/hid.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/printk.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/wait.h>
+
+#include "spi-hid-apple.h"
+
+#define SPIHID_DEF_WAIT msecs_to_jiffies(1000)
+
+#define SPIHID_MAX_INPUT_REPORT_SIZE 0x800
+
+/* support only keyboard, trackpad and management dev for now */
+#define SPIHID_MAX_DEVICES 3
+
+#define SPIHID_DEVICE_ID_MNGT 0x0
+#define SPIHID_DEVICE_ID_KBD 0x1
+#define SPIHID_DEVICE_ID_TP 0x2
+#define SPIHID_DEVICE_ID_INFO 0xd0
+
+#define SPIHID_READ_PACKET 0x20
+#define SPIHID_WRITE_PACKET 0x40
+
+#define SPIHID_DESC_MAX 512
+
+#define SPIHID_SET_LEDS 0x0151 /* caps lock */
+
+#define SPI_RW_CHG_DELAY_US 200 /* 'Inter Stage Us'? */
+
+static const u8 spi_hid_apple_booted[4] = { 0xa0, 0x80, 0x00, 0x00 };
+static const u8 spi_hid_apple_status_ok[4] = { 0xac, 0x27, 0x68, 0xd5 };
+
+struct spihid_interface {
+ struct hid_device *hid;
+ u8 *hid_desc;
+ u32 hid_desc_len;
+ u32 id;
+ unsigned country;
+ u32 max_control_report_len;
+ u32 max_input_report_len;
+ u32 max_output_report_len;
+ u8 name[32];
+ bool ready;
+};
+
+struct spihid_input_report {
+ u8 *buf;
+ u32 length;
+ u32 offset;
+ u8 device;
+ u8 flags;
+};
+
+struct spihid_apple {
+ struct spi_device *spidev;
+
+ struct spihid_apple_ops *ops;
+
+ struct spihid_interface mngt;
+ struct spihid_interface kbd;
+ struct spihid_interface tp;
+
+ wait_queue_head_t wait;
+ struct mutex tx_lock; //< protects against concurrent SPI writes
+
+ struct spi_message rx_msg;
+ struct spi_message tx_msg;
+ struct spi_transfer rx_transfer;
+ struct spi_transfer tx_transfer;
+ struct spi_transfer status_transfer;
+
+ u8 *rx_buf;
+ u8 *tx_buf;
+ u8 *status_buf;
+
+ u8 vendor[32];
+ u8 product[64];
+ u8 serial[32];
+
+ u32 num_devices;
+
+ u32 vendor_id;
+ u32 product_id;
+ u32 version_number;
+
+ u8 msg_id;
+
+ /* fragmented HID report */
+ struct spihid_input_report report;
+
+ /* state tracking flags */
+ bool status_booted;
+
+#ifdef IRQ_WAKE_SUPPORT
+ bool irq_wake_enabled;
+#endif
+};
+
+/**
+ * struct spihid_msg_hdr - common header of protocol messages.
+ *
+ * Each message begins with fixed header, followed by a message-type specific
+ * payload, and ends with a 16-bit crc. Because of the varying lengths of the
+ * payload, the crc is defined at the end of each payload struct, rather than
+ * in this struct.
+ *
+ * @unknown0: request type? output, input (0x10), feature, protocol
+ * @unknown1: maybe report id?
+ * @unknown2: mostly zero, in info request maybe device num
+ * @msgid: incremented on each message, rolls over after 255; there is a
+ * separate counter for each message type.
+ * @rsplen: response length (the exact nature of this field is quite
+ * speculative). On a request/write this is often the same as
+ * @length, though in some cases it has been seen to be much larger
+ * (e.g. 0x400); on a response/read this the same as on the
+ * request; for reads that are not responses it is 0.
+ * @length: length of the remainder of the data in the whole message
+ * structure (after re-assembly in case of being split over
+ * multiple spi-packets), minus the trailing crc. The total size
+ * of a message is therefore @length + 10.
+ */
+
+struct spihid_msg_hdr {
+ u8 unknown0;
+ u8 unknown1;
+ u8 unknown2;
+ u8 id;
+ __le16 rsplen;
+ __le16 length;
+};
+
+/**
+ * struct spihid_transfer_packet - a complete spi packet; always 256 bytes. This carries
+ * the (parts of the) message in the data. But note that this does not
+ * necessarily contain a complete message, as in some cases (e.g. many
+ * fingers pressed) the message is split over multiple packets (see the
+ * @offset, @remain, and @length fields). In general the data parts in
+ * spihid_transfer_packet's are concatenated until @remaining is 0, and the
+ * result is an message.
+ *
+ * @flags: 0x40 = write (to device), 0x20 = read (from device); note that
+ * the response to a write still has 0x40.
+ * @device: 1 = keyboard, 2 = touchpad
+ * @offset: specifies the offset of this packet's data in the complete
+ * message; i.e. > 0 indicates this is a continuation packet (in
+ * the second packet for a message split over multiple packets
+ * this would then be the same as the @length in the first packet)
+ * @remain: number of message bytes remaining in subsequents packets (in
+ * the first packet of a message split over two packets this would
+ * then be the same as the @length in the second packet)
+ * @length: length of the valid data in the @data in this packet
+ * @data: all or part of a message
+ * @crc16: crc over this whole structure minus this @crc16 field. This
+ * covers just this packet, even on multi-packet messages (in
+ * contrast to the crc in the message).
+ */
+struct spihid_transfer_packet {
+ u8 flags;
+ u8 device;
+ __le16 offset;
+ __le16 remain;
+ __le16 length;
+ u8 data[246];
+ __le16 crc16;
+};
+
+/*
+ * how HID is mapped onto the protocol is not fully clear. This are the known
+ * reports/request:
+ *
+ * pkt.flags pkt.dev? msg.u0 msg.u1 msg.u2
+ * info 0x40 0xd0 0x20 0x01 0xd0
+ *
+ * info mngt: 0x40 0xd0 0x20 0x10 0x00
+ * info kbd: 0x40 0xd0 0x20 0x10 0x01
+ * info tp: 0x40 0xd0 0x20 0x10 0x02
+ *
+ * desc kbd: 0x40 0xd0 0x20 0x10 0x01
+ * desc trackpad: 0x40 0xd0 0x20 0x10 0x02
+ *
+ * mt mode: 0x40 0x02 0x52 0x02 0x00 set protocol?
+ * capslock led 0x40 0x01 0x51 0x01 0x00 output report
+ *
+ * report kbd: 0x20 0x01 0x10 0x01 0x00 input report
+ * report tp: 0x20 0x02 0x10 0x02 0x00 input report
+ *
+ */
+
+
+static int spihid_apple_request(struct spihid_apple *spihid, u8 target, u8 unk0,
+ u8 unk1, u8 unk2, u16 resp_len, u8 *buf,
+ size_t len)
+{
+ struct spihid_transfer_packet *pkt;
+ struct spihid_msg_hdr *hdr;
+ u16 crc;
+ int err;
+
+ /* know reports are small enoug to fit in a single packet */
+ if (len > sizeof(pkt->data) - sizeof(*hdr) - sizeof(__le16))
+ return -EINVAL;
+
+ err = mutex_lock_interruptible(&spihid->tx_lock);
+ if (err < 0)
+ return err;
+
+ pkt = (struct spihid_transfer_packet *)spihid->tx_buf;
+
+ memset(pkt, 0, sizeof(*pkt));
+ pkt->flags = SPIHID_WRITE_PACKET;
+ pkt->device = target;
+ pkt->length = cpu_to_le16(sizeof(*hdr) + len + sizeof(__le16));
+
+ hdr = (struct spihid_msg_hdr *)&pkt->data[0];
+ hdr->unknown0 = unk0;
+ hdr->unknown1 = unk1;
+ hdr->unknown2 = unk2;
+ hdr->id = spihid->msg_id++;
+ hdr->rsplen = cpu_to_le16(resp_len);
+ hdr->length = cpu_to_le16(len);
+
+ if (len)
+ memcpy(pkt->data + sizeof(*hdr), buf, len);
+ crc = crc16(0, &pkt->data[0], sizeof(*hdr) + len);
+ put_unaligned_le16(crc, pkt->data + sizeof(*hdr) + len);
+
+ pkt->crc16 = cpu_to_le16(crc16(0, spihid->tx_buf,
+ offsetof(struct spihid_transfer_packet, crc16)));
+
+ memset(spihid->status_buf, 0, sizeof(spi_hid_apple_status_ok));
+
+ err = spi_sync(spihid->spidev, &spihid->tx_msg);
+
+ if (memcmp(spihid->status_buf, spi_hid_apple_status_ok,
+ sizeof(spi_hid_apple_status_ok))) {
+ u8 *b = spihid->status_buf;
+ dev_warn_ratelimited(&spihid->spidev->dev, "status message "
+ "mismatch: %02x %02x %02x %02x\n",
+ b[0], b[1], b[2], b[3]);
+ }
+ mutex_unlock(&spihid->tx_lock);
+ if (err < 0)
+ return err;
+
+ return (int)len;
+}
+
+static struct spihid_apple *spihid_get_data(struct spihid_interface *idev)
+{
+ switch (idev->id) {
+ case SPIHID_DEVICE_ID_KBD:
+ return container_of(idev, struct spihid_apple, kbd);
+ case SPIHID_DEVICE_ID_TP:
+ return container_of(idev, struct spihid_apple, tp);
+ default:
+ return NULL;
+ }
+}
+
+static int apple_ll_start(struct hid_device *hdev)
+{
+ /* no-op SPI transport is already setup */
+ return 0;
+};
+
+static void apple_ll_stop(struct hid_device *hdev)
+{
+ /* no-op, devices will be desstroyed on driver destruction */
+}
+
+static int apple_ll_open(struct hid_device *hdev)
+{
+ struct spihid_apple *spihid;
+ struct spihid_interface *idev = hdev->driver_data;
+
+ if (idev->hid_desc_len == 0) {
+ spihid = spihid_get_data(idev);
+ dev_warn(&spihid->spidev->dev,
+ "HID descriptor missing for dev %u", idev->id);
+ } else
+ idev->ready = true;
+
+ return 0;
+}
+
+static void apple_ll_close(struct hid_device *hdev)
+{
+ struct spihid_interface *idev = hdev->driver_data;
+ idev->ready = false;
+}
+
+static int apple_ll_parse(struct hid_device *hdev)
+{
+ struct spihid_interface *idev = hdev->driver_data;
+
+ return hid_parse_report(hdev, idev->hid_desc, idev->hid_desc_len);
+}
+
+static int apple_ll_raw_request(struct hid_device *hdev,
+ unsigned char reportnum, __u8 *buf, size_t len,
+ unsigned char rtype, int reqtype)
+{
+ struct spihid_interface *idev = hdev->driver_data;
+ struct spihid_apple *spihid = spihid_get_data(idev);
+
+ dev_dbg(&spihid->spidev->dev,
+ "apple_ll_raw_request: device:%u reportnum:%hhu rtype:%hhu",
+ idev->id, reportnum, rtype);
+
+ switch (reqtype) {
+ case HID_REQ_GET_REPORT:
+ return -EINVAL; // spihid_get_raw_report();
+ case HID_REQ_SET_REPORT:
+ if (buf[0] != reportnum)
+ return -EINVAL;
+ if (reportnum != idev->id) {
+ dev_warn(&spihid->spidev->dev,
+ "device:%u reportnum:"
+ "%hhu mismatch",
+ idev->id, reportnum);
+ return -EINVAL;
+ }
+ return spihid_apple_request(spihid, idev->id, 0x52, reportnum, 0x00, 2, buf, len);
+ default:
+ return -EIO;
+ }
+}
+
+static int apple_ll_output_report(struct hid_device *hdev, __u8 *buf,
+ size_t len)
+{
+ struct spihid_interface *idev = hdev->driver_data;
+ struct spihid_apple *spihid = spihid_get_data(idev);
+ if (!spihid)
+ return -1;
+
+ dev_dbg(&spihid->spidev->dev,
+ "apple_ll_output_report: device:%u len:%zu:",
+ idev->id, len);
+ // second idev->id should maybe be buf[0]?
+ return spihid_apple_request(spihid, idev->id, 0x51, idev->id, 0x00, 0, buf, len);
+}
+
+static struct hid_ll_driver apple_hid_ll = {
+ .start = &apple_ll_start,
+ .stop = &apple_ll_stop,
+ .open = &apple_ll_open,
+ .close = &apple_ll_close,
+ .parse = &apple_ll_parse,
+ .raw_request = &apple_ll_raw_request,
+ .output_report = &apple_ll_output_report,
+};
+
+static struct spihid_interface *spihid_get_iface(struct spihid_apple *spihid,
+ u32 iface)
+{
+ switch (iface) {
+ case SPIHID_DEVICE_ID_MNGT:
+ return &spihid->mngt;
+ case SPIHID_DEVICE_ID_KBD:
+ return &spihid->kbd;
+ case SPIHID_DEVICE_ID_TP:
+ return &spihid->tp;
+ default:
+ return NULL;
+ }
+}
+
+static int spihid_verify_msg(struct spihid_apple *spihid, u8 *buf, size_t len)
+{
+ u16 msg_crc, crc;
+ struct device *dev = &spihid->spidev->dev;
+
+ crc = crc16(0, buf, len - sizeof(__le16));
+ msg_crc = get_unaligned_le16(buf + len - sizeof(__le16));
+ if (crc != msg_crc) {
+ dev_warn_ratelimited(dev, "Read message crc mismatch\n");
+ return 0;
+ }
+ return 1;
+}
+
+static bool spihid_status_report(struct spihid_apple *spihid, u8 *pl,
+ size_t len)
+{
+ struct device *dev = &spihid->spidev->dev;
+ dev_dbg(dev, "%s: len: %zu", __func__, len);
+ if (len == 5 && pl[0] == 0xe0)
+ return true;
+
+ return false;
+}
+
+static bool spihid_process_input_report(struct spihid_apple *spihid, u32 device,
+ struct spihid_msg_hdr *hdr, u8 *payload,
+ size_t len)
+{
+ //dev_dbg(&spihid>spidev->dev, "input report: req:%hx iface:%u ", hdr->unknown0, device);
+ if (hdr->unknown0 != 0x10)
+ return false;
+
+ /* HID device as well but Vendor usage only, handle it internally for now */
+ if (device == 0) {
+ if (hdr->unknown1 == 0xe0) {
+ return spihid_status_report(spihid, payload, len);
+ }
+ } else if (device < SPIHID_MAX_DEVICES) {
+ struct spihid_interface *iface =
+ spihid_get_iface(spihid, device);
+ if (iface && iface->hid && iface->ready) {
+ hid_input_report(iface->hid, HID_INPUT_REPORT, payload,
+ len, 1);
+ return true;
+ }
+ } else
+ dev_dbg(&spihid->spidev->dev,
+ "unexpected iface:%u for input report", device);
+
+ return false;
+}
+
+struct spihid_device_info {
+ __le16 u0[2];
+ __le16 num_devices;
+ __le16 vendor_id;
+ __le16 product_id;
+ __le16 version_number;
+ __le16 vendor_str[2]; //< offset and string length
+ __le16 product_str[2]; //< offset and string length
+ __le16 serial_str[2]; //< offset and string length
+};
+
+static bool spihid_process_device_info(struct spihid_apple *spihid, u32 iface,
+ u8 *payload, size_t len)
+{
+ struct device *dev = &spihid->spidev->dev;
+
+ if (iface != SPIHID_DEVICE_ID_INFO)
+ return false;
+
+ if (spihid->vendor_id == 0 &&
+ len >= sizeof(struct spihid_device_info)) {
+ struct spihid_device_info *info =
+ (struct spihid_device_info *)payload;
+ u16 voff, vlen, poff, plen, soff, slen;
+ u32 num_devices;
+
+ num_devices = __le16_to_cpu(info->num_devices);
+
+ if (num_devices < SPIHID_MAX_DEVICES) {
+ dev_err(dev,
+ "Device info reports %u devices, expecting at least 3",
+ num_devices);
+ return false;
+ }
+ spihid->num_devices = num_devices;
+
+ if (spihid->num_devices > SPIHID_MAX_DEVICES) {
+ dev_info(
+ dev,
+ "limiting the number of devices to mngt, kbd and mouse");
+ spihid->num_devices = SPIHID_MAX_DEVICES;
+ }
+
+ spihid->vendor_id = __le16_to_cpu(info->vendor_id);
+ spihid->product_id = __le16_to_cpu(info->product_id);
+ spihid->version_number = __le16_to_cpu(info->version_number);
+
+ voff = __le16_to_cpu(info->vendor_str[0]);
+ vlen = __le16_to_cpu(info->vendor_str[1]);
+
+ if (voff < len && vlen <= len - voff &&
+ vlen < sizeof(spihid->vendor)) {
+ memcpy(spihid->vendor, payload + voff, vlen);
+ spihid->vendor[vlen] = '\0';
+ }
+
+ poff = __le16_to_cpu(info->product_str[0]);
+ plen = __le16_to_cpu(info->product_str[1]);
+
+ if (poff < len && plen <= len - poff &&
+ plen < sizeof(spihid->product)) {
+ memcpy(spihid->product, payload + poff, plen);
+ spihid->product[plen] = '\0';
+ }
+
+ soff = __le16_to_cpu(info->serial_str[0]);
+ slen = __le16_to_cpu(info->serial_str[1]);
+
+ if (soff < len && slen <= len - soff &&
+ slen < sizeof(spihid->serial)) {
+ memcpy(spihid->vendor, payload + soff, slen);
+ spihid->serial[slen] = '\0';
+ }
+
+ wake_up_interruptible(&spihid->wait);
+ }
+ return true;
+}
+
+struct spihid_iface_info {
+ u8 u_0;
+ u8 interface_num;
+ u8 u_2;
+ u8 u_3;
+ u8 u_4;
+ u8 country_code;
+ __le16 max_input_report_len;
+ __le16 max_output_report_len;
+ __le16 max_control_report_len;
+ __le16 name_offset;
+ __le16 name_length;
+};
+
+static bool spihid_process_iface_info(struct spihid_apple *spihid, u32 num,
+ u8 *payload, size_t len)
+{
+ struct spihid_iface_info *info;
+ struct spihid_interface *iface = spihid_get_iface(spihid, num);
+ u32 name_off, name_len;
+
+ if (!iface)
+ return false;
+
+ if (!iface->max_input_report_len) {
+ if (len < sizeof(*info))
+ return false;
+
+ info = (struct spihid_iface_info *)payload;
+
+ iface->max_input_report_len =
+ le16_to_cpu(info->max_input_report_len);
+ iface->max_output_report_len =
+ le16_to_cpu(info->max_output_report_len);
+ iface->max_control_report_len =
+ le16_to_cpu(info->max_control_report_len);
+ iface->country = info->country_code;
+
+ name_off = le16_to_cpu(info->name_offset);
+ name_len = le16_to_cpu(info->name_length);
+
+ if (name_off < len && name_len <= len - name_off &&
+ name_len < sizeof(iface->name)) {
+ memcpy(iface->name, payload + name_off, name_len);
+ iface->name[name_len] = '\0';
+ }
+
+ dev_dbg(&spihid->spidev->dev, "Info for %s, country code: 0x%x",
+ iface->name, iface->country);
+
+ wake_up_interruptible(&spihid->wait);
+ }
+
+ return true;
+}
+
+static int spihid_register_hid_device(struct spihid_apple *spihid,
+ struct spihid_interface *idev, u8 device);
+
+static bool spihid_process_iface_hid_report_desc(struct spihid_apple *spihid,
+ u32 num, u8 *payload,
+ size_t len)
+{
+ struct spihid_interface *iface = spihid_get_iface(spihid, num);
+
+ if (!iface)
+ return false;
+
+ if (iface->hid_desc_len == 0) {
+ if (len > SPIHID_DESC_MAX)
+ return false;
+ memcpy(iface->hid_desc, payload, len);
+ iface->hid_desc_len = len;
+
+ /* do not register the mngt iface as HID device */
+ if (num > 0)
+ spihid_register_hid_device(spihid, iface, num);
+
+ wake_up_interruptible(&spihid->wait);
+ }
+ return true;
+}
+
+static bool spihid_process_response(struct spihid_apple *spihid,
+ struct spihid_msg_hdr *hdr, u8 *payload,
+ size_t len)
+{
+ if (hdr->unknown0 == 0x20) {
+ switch (hdr->unknown1) {
+ case 0x01:
+ return spihid_process_device_info(spihid, hdr->unknown2,
+ payload, len);
+ case 0x02:
+ return spihid_process_iface_info(spihid, hdr->unknown2,
+ payload, len);
+ case 0x10:
+ return spihid_process_iface_hid_report_desc(
+ spihid, hdr->unknown2, payload, len);
+ default:
+ break;
+ }
+ }
+
+ return false;
+}
+
+static void spihid_process_message(struct spihid_apple *spihid, u8 *data,
+ size_t length, u8 device, u8 flags)
+{
+ struct device *dev = &spihid->spidev->dev;
+ struct spihid_msg_hdr *hdr;
+ bool handled = false;
+ u8 *payload;
+
+ if (!spihid_verify_msg(spihid, data, length))
+ return;
+
+ hdr = (struct spihid_msg_hdr *)data;
+
+ if (hdr->length == 0)
+ return;
+
+ payload = data + sizeof(struct spihid_msg_hdr);
+
+ switch (flags) {
+ case SPIHID_READ_PACKET:
+ handled = spihid_process_input_report(spihid, device, hdr,
+ payload, le16_to_cpu(hdr->length));
+ break;
+ case SPIHID_WRITE_PACKET:
+ handled = spihid_process_response(spihid, hdr, payload,
+ le16_to_cpu(hdr->length));
+ break;
+ default:
+ break;
+ }
+
+#if defined(DEBUG) && DEBUG > 1
+ {
+ dev_dbg(dev,
+ "R msg: req:%02hhx rep:%02hhx dev:%02hhx id:%hu len:%hu\n",
+ hdr->unknown0, hdr->unknown1, hdr->unknown2, hdr->id,
+ hdr->length);
+ print_hex_dump_debug("spihid msg: ", DUMP_PREFIX_OFFSET, 16, 1,
+ payload, le16_to_cpu(hdr->length), true);
+ }
+#else
+ if (!handled) {
+ dev_dbg(dev,
+ "R unhandled msg: req:%02hhx rep:%02hhx dev:%02hhx id:%hu len:%hu\n",
+ hdr->unknown0, hdr->unknown1, hdr->unknown2, hdr->id,
+ hdr->length);
+ print_hex_dump_debug("spihid msg: ", DUMP_PREFIX_OFFSET, 16, 1,
+ payload, le16_to_cpu(hdr->length), true);
+ }
+#endif
+}
+
+static void spihid_assemble_message(struct spihid_apple *spihid,
+ struct spihid_transfer_packet *pkt)
+{
+ size_t length, offset, remain;
+ struct device *dev = &spihid->spidev->dev;
+ struct spihid_input_report *rep = &spihid->report;
+
+ length = le16_to_cpu(pkt->length);
+ remain = le16_to_cpu(pkt->remain);
+ offset = le16_to_cpu(pkt->offset);
+
+ if (offset + length + remain > U16_MAX) {
+ return;
+ }
+
+ if (pkt->device != rep->device || pkt->flags != rep->flags ||
+ offset != rep->offset) {
+ rep->device = 0;
+ rep->flags = 0;
+ rep->offset = 0;
+ rep->length = 0;
+ }
+
+ if (offset == 0) {
+ if (rep->offset != 0) {
+ dev_warn(dev, "incomplete report off:%u len:%u",
+ rep->offset, rep->length);
+ }
+ memcpy(rep->buf, pkt->data, length);
+ rep->offset = length;
+ rep->length = length + remain;
+ rep->device = pkt->device;
+ rep->flags = pkt->flags;
+ } else if (offset == rep->offset) {
+ if (offset + length + remain != rep->length) {
+ dev_warn(dev, "incomplete report off:%u len:%u",
+ rep->offset, rep->length);
+ return;
+ }
+ memcpy(rep->buf + offset, pkt->data, length);
+ rep->offset += length;
+
+ if (rep->offset == rep->length) {
+ spihid_process_message(spihid, rep->buf, rep->length,
+ rep->device, rep->flags);
+ rep->device = 0;
+ rep->flags = 0;
+ rep->offset = 0;
+ rep->length = 0;
+ }
+ }
+}
+
+static void spihid_process_read(struct spihid_apple *spihid)
+{
+ u16 crc;
+ size_t length;
+ struct device *dev = &spihid->spidev->dev;
+ struct spihid_transfer_packet *pkt;
+
+ pkt = (struct spihid_transfer_packet *)spihid->rx_buf;
+
+ /* check transfer packet crc */
+ crc = crc16(0, spihid->rx_buf,
+ offsetof(struct spihid_transfer_packet, crc16));
+ if (crc != le16_to_cpu(pkt->crc16)) {
+ dev_warn_ratelimited(dev, "Read package crc mismatch\n");
+ return;
+ }
+
+ length = le16_to_cpu(pkt->length);
+
+ if (length < sizeof(struct spihid_msg_hdr) + 2) {
+ if (length == sizeof(spi_hid_apple_booted) &&
+ !memcmp(pkt->data, spi_hid_apple_booted, length)) {
+ if (!spihid->status_booted) {
+ spihid->status_booted = true;
+ wake_up_interruptible(&spihid->wait);
+ }
+ } else {
+ dev_info(dev, "R short packet: len:%zu\n", length);
+ print_hex_dump(KERN_INFO, "spihid pkt:",
+ DUMP_PREFIX_OFFSET, 16, 1, pkt->data,
+ length, false);
+ }
+ return;
+ }
+
+#if defined(DEBUG) && DEBUG > 1
+ dev_dbg(dev,
+ "R pkt: flags:%02hhx dev:%02hhx off:%hu remain:%hu, len:%zu\n",
+ pkt->flags, pkt->device, pkt->offset, pkt->remain, length);
+#if defined(DEBUG) && DEBUG > 2
+ print_hex_dump_debug("spihid pkt: ", DUMP_PREFIX_OFFSET, 16, 1,
+ spihid->rx_buf,
+ sizeof(struct spihid_transfer_packet), true);
+#endif
+#endif
+
+ if (length > sizeof(pkt->data)) {
+ dev_warn_ratelimited(dev, "Invalid pkt len:%zu", length);
+ return;
+ }
+
+ /* short message */
+ if (pkt->offset == 0 && pkt->remain == 0) {
+ spihid_process_message(spihid, pkt->data, length, pkt->device,
+ pkt->flags);
+ } else {
+ spihid_assemble_message(spihid, pkt);
+ }
+}
+
+static void spihid_read_packet_sync(struct spihid_apple *spihid)
+{
+ int err;
+
+ err = spi_sync(spihid->spidev, &spihid->rx_msg);
+ if (!err) {
+ spihid_process_read(spihid);
+ } else {
+ dev_warn(&spihid->spidev->dev, "RX failed: %d\n", err);
+ }
+}
+
+irqreturn_t spihid_apple_core_irq(int irq, void *data)
+{
+ struct spi_device *spi = data;
+ struct spihid_apple *spihid = spi_get_drvdata(spi);
+
+ spihid_read_packet_sync(spihid);
+
+ return IRQ_HANDLED;
+}
+EXPORT_SYMBOL_GPL(spihid_apple_core_irq);
+
+static void spihid_apple_setup_spi_msgs(struct spihid_apple *spihid)
+{
+ memset(&spihid->rx_transfer, 0, sizeof(spihid->rx_transfer));
+
+ spihid->rx_transfer.rx_buf = spihid->rx_buf;
+ spihid->rx_transfer.len = sizeof(struct spihid_transfer_packet);
+
+ spi_message_init(&spihid->rx_msg);
+ spi_message_add_tail(&spihid->rx_transfer, &spihid->rx_msg);
+
+ memset(&spihid->tx_transfer, 0, sizeof(spihid->rx_transfer));
+ memset(&spihid->status_transfer, 0, sizeof(spihid->status_transfer));
+
+ spihid->tx_transfer.tx_buf = spihid->tx_buf;
+ spihid->tx_transfer.len = sizeof(struct spihid_transfer_packet);
+ spihid->tx_transfer.delay.unit = SPI_DELAY_UNIT_USECS;
+ spihid->tx_transfer.delay.value = SPI_RW_CHG_DELAY_US;
+
+ spihid->status_transfer.rx_buf = spihid->status_buf;
+ spihid->status_transfer.len = sizeof(spi_hid_apple_status_ok);
+
+ spi_message_init(&spihid->tx_msg);
+ spi_message_add_tail(&spihid->tx_transfer, &spihid->tx_msg);
+ spi_message_add_tail(&spihid->status_transfer, &spihid->tx_msg);
+}
+
+static int spihid_apple_setup_spi(struct spihid_apple *spihid)
+{
+ spihid_apple_setup_spi_msgs(spihid);
+
+ return spihid->ops->power_on(spihid->ops);
+}
+
+static int spihid_register_hid_device(struct spihid_apple *spihid,
+ struct spihid_interface *iface, u8 device)
+{
+ int ret;
+ struct hid_device *hid;
+
+ iface->id = device;
+
+ hid = hid_allocate_device();
+ if (IS_ERR(hid))
+ return PTR_ERR(hid);
+
+ strscpy(hid->name, spihid->product, sizeof(hid->name));
+ snprintf(hid->phys, sizeof(hid->phys), "%s (%hhx)",
+ dev_name(&spihid->spidev->dev), device);
+ strscpy(hid->uniq, spihid->serial, sizeof(hid->uniq));
+
+ hid->ll_driver = &apple_hid_ll;
+ hid->bus = BUS_SPI;
+ hid->vendor = spihid->vendor_id;
+ hid->product = spihid->product_id;
+ hid->version = spihid->version_number;
+
+ if (device == SPIHID_DEVICE_ID_KBD)
+ hid->type = HID_TYPE_SPI_KEYBOARD;
+ else if (device == SPIHID_DEVICE_ID_TP)
+ hid->type = HID_TYPE_SPI_MOUSE;
+
+ hid->country = iface->country;
+ hid->dev.parent = &spihid->spidev->dev;
+ hid->driver_data = iface;
+
+ ret = hid_add_device(hid);
+ if (ret < 0) {
+ hid_destroy_device(hid);
+ dev_warn(&spihid->spidev->dev,
+ "Failed to register hid device %hhu", device);
+ return ret;
+ }
+
+ iface->hid = hid;
+
+ return 0;
+}
+
+static void spihid_destroy_hid_device(struct spihid_interface *iface)
+{
+ if (iface->hid) {
+ hid_destroy_device(iface->hid);
+ iface->hid = NULL;
+ }
+ iface->ready = false;
+}
+
+int spihid_apple_core_probe(struct spi_device *spi, struct spihid_apple_ops *ops)
+{
+ struct device *dev = &spi->dev;
+ struct spihid_apple *spihid;
+ int err, i;
+
+ if (!ops || !ops->power_on || !ops->power_off || !ops->enable_irq || !ops->disable_irq)
+ return -EINVAL;
+
+ spihid = devm_kzalloc(dev, sizeof(*spihid), GFP_KERNEL);
+ if (!spihid)
+ return -ENOMEM;
+
+ spihid->ops = ops;
+ spihid->spidev = spi;
+
+ // init spi
+ spi_set_drvdata(spi, spihid);
+
+ /* allocate SPI buffers */
+ spihid->rx_buf = devm_kmalloc(
+ &spi->dev, sizeof(struct spihid_transfer_packet), GFP_KERNEL);
+ spihid->tx_buf = devm_kmalloc(
+ &spi->dev, sizeof(struct spihid_transfer_packet), GFP_KERNEL);
+ spihid->status_buf = devm_kmalloc(
+ &spi->dev, sizeof(spi_hid_apple_status_ok), GFP_KERNEL);
+
+ if (!spihid->rx_buf || !spihid->tx_buf || !spihid->status_buf)
+ return -ENOMEM;
+
+ spihid->report.buf =
+ devm_kmalloc(dev, SPIHID_MAX_INPUT_REPORT_SIZE, GFP_KERNEL);
+
+ spihid->kbd.hid_desc = devm_kmalloc(dev, SPIHID_DESC_MAX, GFP_KERNEL);
+ spihid->tp.hid_desc = devm_kmalloc(dev, SPIHID_DESC_MAX, GFP_KERNEL);
+
+ if (!spihid->report.buf || !spihid->kbd.hid_desc ||
+ !spihid->tp.hid_desc)
+ return -ENOMEM;
+
+ init_waitqueue_head(&spihid->wait);
+
+ mutex_init(&spihid->tx_lock);
+
+ /* Init spi transfer buffers and power device on */
+ err = spihid_apple_setup_spi(spihid);
+ if (err < 0)
+ goto error;
+
+ /* enable HID irq */
+ spihid->ops->enable_irq(spihid->ops);
+
+ // wait for boot message
+ err = wait_event_interruptible_timeout(spihid->wait,
+ spihid->status_booted,
+ msecs_to_jiffies(1000));
+ if (err == 0)
+ err = -ENODEV;
+ if (err < 0) {
+ dev_err(dev, "waiting for device boot failed: %d", err);
+ goto error;
+ }
+
+ /* request device information */
+ dev_dbg(dev, "request device info");
+ spihid_apple_request(spihid, 0xd0, 0x20, 0x01, 0xd0, 0, NULL, 0);
+ err = wait_event_interruptible_timeout(spihid->wait, spihid->vendor_id,
+ SPIHID_DEF_WAIT);
+ if (err == 0)
+ err = -ENODEV;
+ if (err < 0) {
+ dev_err(dev, "waiting for device info failed: %d", err);
+ goto error;
+ }
+
+ /* request interface information */
+ for (i = 0; i < spihid->num_devices; i++) {
+ struct spihid_interface *iface = spihid_get_iface(spihid, i);
+ if (!iface)
+ continue;
+ dev_dbg(dev, "request interface info 0x%02x", i);
+ spihid_apple_request(spihid, 0xd0, 0x20, 0x02, i,
+ SPIHID_DESC_MAX, NULL, 0);
+ err = wait_event_interruptible_timeout(
+ spihid->wait, iface->max_input_report_len,
+ SPIHID_DEF_WAIT);
+ }
+
+ /* request HID report descriptors */
+ for (i = 1; i < spihid->num_devices; i++) {
+ struct spihid_interface *iface = spihid_get_iface(spihid, i);
+ if (!iface)
+ continue;
+ dev_dbg(dev, "request hid report desc 0x%02x", i);
+ spihid_apple_request(spihid, 0xd0, 0x20, 0x10, i,
+ SPIHID_DESC_MAX, NULL, 0);
+ wait_event_interruptible_timeout(
+ spihid->wait, iface->hid_desc_len, SPIHID_DEF_WAIT);
+ }
+
+ return 0;
+error:
+ return err;
+}
+EXPORT_SYMBOL_GPL(spihid_apple_core_probe);
+
+void spihid_apple_core_remove(struct spi_device *spi)
+{
+ struct spihid_apple *spihid = spi_get_drvdata(spi);
+
+ /* destroy input devices */
+
+ spihid_destroy_hid_device(&spihid->tp);
+ spihid_destroy_hid_device(&spihid->kbd);
+
+ /* disable irq */
+ spihid->ops->disable_irq(spihid->ops);
+
+ /* power SPI device down */
+ spihid->ops->power_off(spihid->ops);
+}
+EXPORT_SYMBOL_GPL(spihid_apple_core_remove);
+
+void spihid_apple_core_shutdown(struct spi_device *spi)
+{
+ struct spihid_apple *spihid = spi_get_drvdata(spi);
+
+ /* disable irq */
+ spihid->ops->disable_irq(spihid->ops);
+
+ /* power SPI device down */
+ spihid->ops->power_off(spihid->ops);
+}
+EXPORT_SYMBOL_GPL(spihid_apple_core_shutdown);
+
+#ifdef CONFIG_PM_SLEEP
+static int spihid_apple_core_suspend(struct device *dev)
+{
+ int ret;
+#ifdef IRQ_WAKE_SUPPORT
+ int wake_status;
+#endif
+ struct spihid_apple *spihid = spi_get_drvdata(to_spi_device(dev));
+
+ if (spihid->tp.hid) {
+ ret = hid_driver_suspend(spihid->tp.hid, PMSG_SUSPEND);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (spihid->kbd.hid) {
+ ret = hid_driver_suspend(spihid->kbd.hid, PMSG_SUSPEND);
+ if (ret < 0) {
+ if (spihid->tp.hid)
+ hid_driver_resume(spihid->tp.hid);
+ return ret;
+ }
+ }
+
+ /* Save some power */
+ spihid->ops->disable_irq(spihid->ops);
+
+#ifdef IRQ_WAKE_SUPPORT
+ if (device_may_wakeup(dev)) {
+ wake_status = spihid->ops->enable_irq_wake(spihid->ops);
+ if (!wake_status)
+ spihid->irq_wake_enabled = true;
+ else
+ dev_warn(dev, "Failed to enable irq wake: %d\n",
+ wake_status);
+ } else {
+ spihid->ops->power_off(spihid->ops);
+ }
+#else
+ spihid->ops->power_off(spihid->ops);
+#endif
+
+ return 0;
+}
+
+static int spihid_apple_core_resume(struct device *dev)
+{
+ int ret_tp = 0, ret_kbd = 0;
+ struct spihid_apple *spihid = spi_get_drvdata(to_spi_device(dev));
+#ifdef IRQ_WAKE_SUPPORT
+ int wake_status;
+
+ if (!device_may_wakeup(dev)) {
+ spihid->ops->power_on(spihid->ops);
+ } else if (spihid->irq_wake_enabled) {
+ wake_status = spihid->ops->disable_irq_wake(spihid->ops);
+ if (!wake_status)
+ spihid->irq_wake_enabled = false;
+ else
+ dev_warn(dev, "Failed to disable irq wake: %d\n",
+ wake_status);
+ }
+#endif
+
+ spihid->ops->enable_irq(spihid->ops);
+ spihid->ops->power_on(spihid->ops);
+
+ if (spihid->tp.hid)
+ ret_tp = hid_driver_reset_resume(spihid->tp.hid);
+ if (spihid->kbd.hid)
+ ret_kbd = hid_driver_reset_resume(spihid->kbd.hid);
+
+ if (ret_tp < 0)
+ return ret_tp;
+
+ return ret_kbd;
+}
+#endif
+
+const struct dev_pm_ops spihid_apple_core_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(spihid_apple_core_suspend,
+ spihid_apple_core_resume)
+};
+EXPORT_SYMBOL_GPL(spihid_apple_core_pm);
+
+MODULE_DESCRIPTION("Apple SPI HID transport driver");
+MODULE_AUTHOR("Janne Grunau <j@jannau.net>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/spi-hid/spi-hid-apple-of.c b/drivers/hid/spi-hid/spi-hid-apple-of.c
new file mode 100644
index 0000000..6531f15
--- /dev/null
+++ b/drivers/hid/spi-hid/spi-hid-apple-of.c
@@ -0,0 +1,152 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0
+ *
+ * Apple SPI HID transport driver - Open Firmware
+ *
+ * Copyright (C) The Asahi Linux Contributors
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+
+#include "spi-hid-apple.h"
+
+
+struct spihid_apple_of {
+ struct spihid_apple_ops ops;
+
+ struct gpio_desc *enable_gpio;
+ int irq;
+};
+
+static int spihid_apple_of_power_on(struct spihid_apple_ops *ops)
+{
+ struct spihid_apple_of *sh_of = container_of(ops, struct spihid_apple_of, ops);
+
+ /* reset the controller on boot */
+ gpiod_direction_output(sh_of->enable_gpio, 1);
+ msleep(5);
+ gpiod_direction_output(sh_of->enable_gpio, 0);
+ msleep(5);
+ /* turn SPI device on */
+ gpiod_direction_output(sh_of->enable_gpio, 1);
+ msleep(50);
+
+ return 0;
+}
+
+static int spihid_apple_of_power_off(struct spihid_apple_ops *ops)
+{
+ struct spihid_apple_of *sh_of = container_of(ops, struct spihid_apple_of, ops);
+
+ /* turn SPI device off */
+ gpiod_direction_output(sh_of->enable_gpio, 0);
+
+ return 0;
+}
+
+static int spihid_apple_of_enable_irq(struct spihid_apple_ops *ops)
+{
+ struct spihid_apple_of *sh_of = container_of(ops, struct spihid_apple_of, ops);
+
+ enable_irq(sh_of->irq);
+
+ return 0;
+}
+
+static int spihid_apple_of_disable_irq(struct spihid_apple_ops *ops)
+{
+ struct spihid_apple_of *sh_of = container_of(ops, struct spihid_apple_of, ops);
+
+ disable_irq(sh_of->irq);
+
+ return 0;
+}
+
+static int spihid_apple_of_enable_irq_wake(struct spihid_apple_ops *ops)
+{
+ struct spihid_apple_of *sh_of = container_of(ops, struct spihid_apple_of, ops);
+
+ return enable_irq_wake(sh_of->irq);
+}
+
+static int spihid_apple_of_disable_irq_wake(struct spihid_apple_ops *ops)
+{
+ struct spihid_apple_of *sh_of = container_of(ops, struct spihid_apple_of, ops);
+
+ return disable_irq_wake(sh_of->irq);
+}
+
+static int spihid_apple_of_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct spihid_apple_of *spihid_of;
+ int err;
+
+ spihid_of = devm_kzalloc(dev, sizeof(*spihid_of), GFP_KERNEL);
+ if (!spihid_of)
+ return -ENOMEM;
+
+ spihid_of->ops.power_on = spihid_apple_of_power_on;
+ spihid_of->ops.power_off = spihid_apple_of_power_off;
+ spihid_of->ops.enable_irq = spihid_apple_of_enable_irq;
+ spihid_of->ops.disable_irq = spihid_apple_of_disable_irq;
+ spihid_of->ops.enable_irq_wake = spihid_apple_of_enable_irq_wake;
+ spihid_of->ops.disable_irq_wake = spihid_apple_of_disable_irq_wake;
+
+ spihid_of->enable_gpio = devm_gpiod_get_index(dev, "spien", 0, 0);
+ if (IS_ERR(spihid_of->enable_gpio)) {
+ err = PTR_ERR(spihid_of->enable_gpio);
+ dev_err(dev, "failed to get 'spien' gpio pin: %d", err);
+ return err;
+ }
+
+ spihid_of->irq = of_irq_get(dev->of_node, 0);
+ if (spihid_of->irq < 0) {
+ err = spihid_of->irq;
+ dev_err(dev, "failed to get 'extended-irq': %d", err);
+ return err;
+ }
+ err = devm_request_threaded_irq(dev, spihid_of->irq, NULL,
+ spihid_apple_core_irq, IRQF_ONESHOT | IRQF_NO_AUTOEN,
+ "spi-hid-apple-irq", spi);
+ if (err < 0) {
+ dev_err(dev, "failed to request extended-irq %d: %d",
+ spihid_of->irq, err);
+ return err;
+ }
+
+ return spihid_apple_core_probe(spi, &spihid_of->ops);
+}
+
+static const struct of_device_id spihid_apple_of_match[] = {
+ { .compatible = "apple,spi-hid-transport" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, spihid_apple_of_match);
+
+static struct spi_device_id spihid_apple_of_id[] = {
+ { "spi-hid-transport", 0 },
+ {}
+};
+MODULE_DEVICE_TABLE(spi, spihid_apple_of_id);
+
+static struct spi_driver spihid_apple_of_driver = {
+ .driver = {
+ .name = "spi-hid-apple-of",
+ .pm = &spihid_apple_core_pm,
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(spihid_apple_of_match),
+ },
+
+ .id_table = spihid_apple_of_id,
+ .probe = spihid_apple_of_probe,
+ .remove = spihid_apple_core_remove,
+ .shutdown = spihid_apple_core_shutdown,
+};
+
+module_spi_driver(spihid_apple_of_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/spi-hid/spi-hid-apple.h b/drivers/hid/spi-hid/spi-hid-apple.h
new file mode 100644
index 0000000..9abecd1
--- /dev/null
+++ b/drivers/hid/spi-hid/spi-hid-apple.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0-only OR MIT */
+
+#ifndef SPI_HID_APPLE_H
+#define SPI_HID_APPLE_H
+
+#include <linux/interrupt.h>
+#include <linux/spi/spi.h>
+
+/**
+ * struct spihid_apple_ops - Ops to control the device from the core driver.
+ *
+ * @power_on: reset and power the device on.
+ * @power_off: power the device off.
+ * @enable_irq: enable irq or ACPI gpe.
+ * @disable_irq: disable irq or ACPI gpe.
+ */
+
+struct spihid_apple_ops {
+ int (*power_on)(struct spihid_apple_ops *ops);
+ int (*power_off)(struct spihid_apple_ops *ops);
+ int (*enable_irq)(struct spihid_apple_ops *ops);
+ int (*disable_irq)(struct spihid_apple_ops *ops);
+ int (*enable_irq_wake)(struct spihid_apple_ops *ops);
+ int (*disable_irq_wake)(struct spihid_apple_ops *ops);
+};
+
+irqreturn_t spihid_apple_core_irq(int irq, void *data);
+
+int spihid_apple_core_probe(struct spi_device *spi, struct spihid_apple_ops *ops);
+void spihid_apple_core_remove(struct spi_device *spi);
+void spihid_apple_core_shutdown(struct spi_device *spi);
+
+extern const struct dev_pm_ops spihid_apple_core_pm;
+
+#endif /* SPI_HID_APPLE_H */
diff --git a/drivers/i2c/busses/i2c-pasemi-core.c b/drivers/i2c/busses/i2c-pasemi-core.c
index 7d54a9f..a76f063 100644
--- a/drivers/i2c/busses/i2c-pasemi-core.c
+++ b/drivers/i2c/busses/i2c-pasemi-core.c
@@ -5,6 +5,7 @@
* SMBus host driver for PA Semi PWRficient
*/
+#include <linux/bitfield.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/kernel.h>
@@ -20,27 +21,39 @@
/* Register offsets */
#define REG_MTXFIFO 0x00
#define REG_MRXFIFO 0x04
+#define REG_XFSTA 0x0c
#define REG_SMSTA 0x14
#define REG_IMASK 0x18
#define REG_CTL 0x1c
#define REG_REV 0x28
/* Register defs */
-#define MTXFIFO_READ 0x00000400
-#define MTXFIFO_STOP 0x00000200
-#define MTXFIFO_START 0x00000100
-#define MTXFIFO_DATA_M 0x000000ff
+#define MTXFIFO_READ BIT(10)
+#define MTXFIFO_STOP BIT(9)
+#define MTXFIFO_START BIT(8)
+#define MTXFIFO_DATA_M GENMASK(7, 0)
-#define MRXFIFO_EMPTY 0x00000100
-#define MRXFIFO_DATA_M 0x000000ff
+#define MRXFIFO_EMPTY BIT(8)
+#define MRXFIFO_DATA_M GENMASK(7, 0)
-#define SMSTA_XEN 0x08000000
-#define SMSTA_MTN 0x00200000
+#define SMSTA_XIP BIT(28)
+#define SMSTA_XEN BIT(27)
+#define SMSTA_JMD BIT(25)
+#define SMSTA_JAM BIT(24)
+#define SMSTA_MTO BIT(23)
+#define SMSTA_MTA BIT(22)
+#define SMSTA_MTN BIT(21)
+#define SMSTA_MRNE BIT(19)
+#define SMSTA_MTE BIT(16)
+#define SMSTA_TOM BIT(6)
-#define CTL_MRR 0x00000400
-#define CTL_MTR 0x00000200
-#define CTL_EN 0x00000800
-#define CTL_CLK_M 0x000000ff
+#define CTL_EN BIT(11)
+#define CTL_MRR BIT(10)
+#define CTL_MTR BIT(9)
+#define CTL_UJM BIT(8)
+#define CTL_CLK_M GENMASK(7, 0)
+
+#define TRANSFER_TIMEOUT_MS 100
static inline void reg_write(struct pasemi_smbus *smbus, int reg, int val)
{
@@ -61,7 +74,7 @@ static inline int reg_read(struct pasemi_smbus *smbus, int reg)
static void pasemi_reset(struct pasemi_smbus *smbus)
{
- u32 val = (CTL_MTR | CTL_MRR | (smbus->clk_div & CTL_CLK_M));
+ u32 val = (CTL_MTR | CTL_MRR | CTL_UJM | (smbus->clk_div & CTL_CLK_M));
if (smbus->hw_rev >= 6)
val |= CTL_EN;
@@ -70,23 +83,51 @@ static void pasemi_reset(struct pasemi_smbus *smbus)
reinit_completion(&smbus->irq_completion);
}
-static void pasemi_smb_clear(struct pasemi_smbus *smbus)
+static int pasemi_smb_clear(struct pasemi_smbus *smbus)
{
- unsigned int status;
+ unsigned int status, xfstatus;
+ int timeout = TRANSFER_TIMEOUT_MS;
status = reg_read(smbus, REG_SMSTA);
+
+ /* First wait for the bus to go idle */
+ while ((status & (SMSTA_XIP | SMSTA_JAM)) && timeout--) {
+ msleep(1);
+ status = reg_read(smbus, REG_SMSTA);
+ }
+
+ xfstatus = reg_read(smbus, REG_XFSTA);
+
+ if (timeout < 0) {
+ dev_warn(smbus->dev, "Bus is still stuck (status 0x%08x xfstatus 0x%08x)\n",
+ status, xfstatus);
+ return -EIO;
+ }
+
+ /* If any badness happened or there is data in the FIFOs, reset the FIFOs */
+ if ((status & (SMSTA_MRNE | SMSTA_JMD | SMSTA_MTO | SMSTA_TOM | SMSTA_MTN | SMSTA_MTA)) ||
+ !(status & SMSTA_MTE)) {
+ dev_warn(smbus->dev, "Issuing reset due to status 0x%08x (xfstatus 0x%08x)\n",
+ status, xfstatus);
+ pasemi_reset(smbus);
+ }
+
+ /* Clear the flags */
reg_write(smbus, REG_SMSTA, status);
+
+ return 0;
}
static int pasemi_smb_waitready(struct pasemi_smbus *smbus)
{
- int timeout = 100;
+ int timeout = TRANSFER_TIMEOUT_MS;
unsigned int status;
if (smbus->use_irq) {
reinit_completion(&smbus->irq_completion);
- reg_write(smbus, REG_IMASK, SMSTA_XEN | SMSTA_MTN);
- wait_for_completion_timeout(&smbus->irq_completion, msecs_to_jiffies(100));
+ /* XEN should be set when a transaction terminates, whether due to error or not */
+ reg_write(smbus, REG_IMASK, SMSTA_XEN);
+ wait_for_completion_timeout(&smbus->irq_completion, msecs_to_jiffies(timeout));
reg_write(smbus, REG_IMASK, 0);
status = reg_read(smbus, REG_SMSTA);
} else {
@@ -97,16 +138,32 @@ static int pasemi_smb_waitready(struct pasemi_smbus *smbus)
}
}
+ /* Controller timeout? */
+ if (status & SMSTA_TOM) {
+ dev_warn(smbus->dev, "Controller timeout, status 0x%08x\n", status);
+ return -EIO;
+ }
+
+ /* Peripheral timeout? */
+ if (status & SMSTA_MTO) {
+ dev_warn(smbus->dev, "Peripheral timeout, status 0x%08x\n", status);
+ return -ETIME;
+ }
+
+ /* Still stuck in a transaction? */
+ if (status & SMSTA_XIP) {
+ dev_warn(smbus->dev, "Bus stuck, status 0x%08x\n", status);
+ return -EIO;
+ }
+
+ /* Arbitration loss? */
+ if (status & SMSTA_MTA)
+ return -EBUSY;
+
/* Got NACK? */
if (status & SMSTA_MTN)
return -ENXIO;
- if (timeout < 0) {
- dev_warn(smbus->dev, "Timeout, status 0x%08x\n", status);
- reg_write(smbus, REG_SMSTA, status);
- return -ETIME;
- }
-
/* Clear XEN */
reg_write(smbus, REG_SMSTA, SMSTA_XEN);
@@ -167,7 +224,8 @@ static int pasemi_i2c_xfer(struct i2c_adapter *adapter,
struct pasemi_smbus *smbus = adapter->algo_data;
int ret, i;
- pasemi_smb_clear(smbus);
+ if (pasemi_smb_clear(smbus))
+ return -EIO;
ret = 0;
@@ -190,7 +248,8 @@ static int pasemi_smb_xfer(struct i2c_adapter *adapter,
addr <<= 1;
read_flag = read_write == I2C_SMBUS_READ;
- pasemi_smb_clear(smbus);
+ if (pasemi_smb_clear(smbus))
+ return -EIO;
switch (size) {
case I2C_SMBUS_QUICK:
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 6ba984d..52ec588 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -939,4 +939,16 @@
To compile this driver as a module, choose M here: the
module will be called stpmic1_onkey.
+config INPUT_MACSMC_HID
+ tristate "Apple Mac SMC lid/buttons"
+ depends on APPLE_SMC
+ default ARCH_APPLE
+ help
+ Say Y here if you want to use the input events delivered via the
+ SMC controller on Apple Mac machines using the macsmc driver.
+ This includes lid open/close and the power button.
+
+ To compile this driver as a module, choose M here: the
+ module will be called macsmc-hid.
+
endif
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 04296a4..6dc2e37 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -49,6 +49,7 @@
obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o
obj-$(CONFIG_INPUT_KXTJ9) += kxtj9.o
obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o
+obj-$(CONFIG_INPUT_MACSMC_HID) += macsmc-hid.o
obj-$(CONFIG_INPUT_MAX77650_ONKEY) += max77650-onkey.o
obj-$(CONFIG_INPUT_MAX77693_HAPTIC) += max77693-haptic.o
obj-$(CONFIG_INPUT_MAX8925_ONKEY) += max8925_onkey.o
diff --git a/drivers/input/misc/macsmc-hid.c b/drivers/input/misc/macsmc-hid.c
new file mode 100644
index 0000000..49296cb
--- /dev/null
+++ b/drivers/input/misc/macsmc-hid.c
@@ -0,0 +1,196 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple SMC input event driver
+ * Copyright The Asahi Linux Contributors
+ *
+ * This driver exposes HID events from the SMC as an input device.
+ * This includes the lid open/close and power button notifications.
+ */
+
+#include <linux/device.h>
+#include <linux/input.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/macsmc.h>
+#include <linux/reboot.h>
+
+struct macsmc_hid {
+ struct device *dev;
+ struct apple_smc *smc;
+ struct input_dev *input;
+ struct notifier_block nb;
+ bool wakeup_mode;
+};
+
+#define SMC_EV_BTN 0x7201
+#define SMC_EV_LID 0x7203
+
+#define BTN_POWER 0x01
+#define BTN_TOUCHID 0x06
+#define BTN_POWER_HELD1 0xfe
+#define BTN_POWER_HELD2 0x00
+
+static int macsmc_hid_event(struct notifier_block *nb, unsigned long event, void *data)
+{
+ struct macsmc_hid *smchid = container_of(nb, struct macsmc_hid, nb);
+ u16 type = event >> 16;
+ u8 d1 = (event >> 8) & 0xff;
+ u8 d2 = event & 0xff;
+
+ switch (type) {
+ case SMC_EV_BTN:
+ switch (d1) {
+ case BTN_POWER:
+ case BTN_TOUCHID:
+ if (smchid->wakeup_mode) {
+ if (d2) {
+ dev_info(smchid->dev, "Button wakeup\n");
+ pm_wakeup_hard_event(smchid->dev);
+ }
+ } else {
+ input_report_key(smchid->input, KEY_POWER, d2);
+ input_sync(smchid->input);
+ }
+ break;
+ case BTN_POWER_HELD1:
+ /*
+ * TODO: is this pre-warning useful?
+ */
+ if (d2)
+ dev_warn(smchid->dev, "Power button held down\n");
+ break;
+ case BTN_POWER_HELD2:
+ /*
+ * If we get here, we have about 4 seconds before forced shutdown.
+ * Try to do an emergency shutdown to make sure the NVMe cache is
+ * flushed. macOS actually does this by panicing (!)...
+ */
+ if (d2) {
+ dev_crit(smchid->dev, "Triggering forced shutdown!\n");
+ if (kernel_can_power_off())
+ kernel_power_off();
+ else /* Missing macsmc-reboot driver? */
+ kernel_restart("SMC power button triggered restart");
+ }
+ break;
+ default:
+ dev_info(smchid->dev, "Unknown SMC button event: %02x %02x\n", d1, d2);
+ break;
+ }
+ return NOTIFY_OK;
+ case SMC_EV_LID:
+ if (smchid->wakeup_mode && !d1) {
+ dev_info(smchid->dev, "Lid wakeup\n");
+ pm_wakeup_hard_event(smchid->dev);
+ }
+ input_report_switch(smchid->input, SW_LID, d1);
+ input_sync(smchid->input);
+ return NOTIFY_OK;
+ }
+
+ return NOTIFY_DONE;
+}
+
+static int macsmc_hid_probe(struct platform_device *pdev)
+{
+ struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent);
+ struct macsmc_hid *smchid;
+ bool have_lid, have_power;
+ int ret;
+
+ have_lid = apple_smc_key_exists(smc, SMC_KEY(MSLD));
+ have_power = apple_smc_key_exists(smc, SMC_KEY(bHLD));
+
+ if (!have_lid && !have_power)
+ return -ENODEV;
+
+ smchid = devm_kzalloc(&pdev->dev, sizeof(*smchid), GFP_KERNEL);
+ if (!smchid)
+ return -ENOMEM;
+
+ smchid->dev = &pdev->dev;
+ smchid->smc = smc;
+ platform_set_drvdata(pdev, smchid);
+
+ smchid->input = devm_input_allocate_device(&pdev->dev);
+ if (!smchid->input)
+ return -ENOMEM;
+
+ smchid->input->phys = "macsmc-hid (0)";
+ smchid->input->name = "Apple SMC power/lid events";
+
+ if (have_lid)
+ input_set_capability(smchid->input, EV_SW, SW_LID);
+ if (have_power)
+ input_set_capability(smchid->input, EV_KEY, KEY_POWER);
+
+ ret = input_register_device(smchid->input);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register input device: %d\n", ret);
+ return ret;
+ }
+
+ if (have_lid) {
+ u8 val;
+
+ ret = apple_smc_read_u8(smc, SMC_KEY(MSLD), &val);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to read initial lid state\n");
+ } else {
+ input_report_switch(smchid->input, SW_LID, val);
+ }
+ }
+ if (have_power) {
+ u32 val;
+
+ ret = apple_smc_read_u32(smc, SMC_KEY(bHLD), &val);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to read initial power button state\n");
+ } else {
+ input_report_key(smchid->input, KEY_POWER, val & 1);
+ }
+ }
+
+ input_sync(smchid->input);
+
+ smchid->nb.notifier_call = macsmc_hid_event;
+ apple_smc_register_notifier(smc, &smchid->nb);
+
+ device_init_wakeup(&pdev->dev, 1);
+
+ return 0;
+}
+
+static int macsmc_hid_pm_prepare(struct device *dev)
+{
+ struct macsmc_hid *smchid = dev_get_drvdata(dev);
+
+ smchid->wakeup_mode = true;
+ return 0;
+}
+
+static void macsmc_hid_pm_complete(struct device *dev)
+{
+ struct macsmc_hid *smchid = dev_get_drvdata(dev);
+
+ smchid->wakeup_mode = false;
+}
+
+static const struct dev_pm_ops macsmc_hid_pm_ops = {
+ .prepare = macsmc_hid_pm_prepare,
+ .complete = macsmc_hid_pm_complete,
+};
+
+static struct platform_driver macsmc_hid_driver = {
+ .driver = {
+ .name = "macsmc-hid",
+ .owner = THIS_MODULE,
+ .pm = &macsmc_hid_pm_ops,
+ },
+ .probe = macsmc_hid_probe,
+};
+module_platform_driver(macsmc_hid_driver);
+
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_DESCRIPTION("Apple SMC GPIO driver");
+MODULE_ALIAS("platform:macsmc-hid");
diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig
index 7673bb8..2f3b114 100644
--- a/drivers/iommu/Kconfig
+++ b/drivers/iommu/Kconfig
@@ -301,6 +301,7 @@
depends on !GENERIC_ATOMIC64 # for IOMMU_IO_PGTABLE_DART
select IOMMU_API
select IOMMU_IO_PGTABLE_DART
+ select OF_IOMMU
default ARCH_APPLE
help
Support for Apple DART (Device Address Resolution Table) IOMMUs
diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c
index ee05f48..62fe204 100644
--- a/drivers/iommu/apple-dart.c
+++ b/drivers/iommu/apple-dart.c
@@ -21,6 +21,7 @@
#include <linux/io-pgtable.h>
#include <linux/iommu.h>
#include <linux/iopoll.h>
+#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
@@ -28,6 +29,7 @@
#include <linux/of_platform.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/swab.h>
#include <linux/types.h>
@@ -121,6 +123,8 @@
#define DART_T8110_ERROR_ADDR_LO 0x170
#define DART_T8110_ERROR_ADDR_HI 0x174
+#define DART_T8110_ERROR_STREAMS 0x1c0
+
#define DART_T8110_PROTECT 0x200
#define DART_T8110_UNPROTECT 0x204
#define DART_T8110_PROTECT_LOCK 0x208
@@ -132,6 +136,7 @@
#define DART_T8110_TCR 0x1000
#define DART_T8110_TCR_REMAP GENMASK(11, 8)
#define DART_T8110_TCR_REMAP_EN BIT(7)
+#define DART_T8110_TCR_FOUR_LEVEL BIT(3)
#define DART_T8110_TCR_BYPASS_DAPF BIT(2)
#define DART_T8110_TCR_BYPASS_DART BIT(1)
#define DART_T8110_TCR_TRANSLATE_ENABLE BIT(0)
@@ -165,22 +170,23 @@ struct apple_dart_hw {
int max_sid_count;
- u64 lock;
- u64 lock_bit;
+ u32 lock;
+ u32 lock_bit;
- u64 error;
+ u32 error;
- u64 enable_streams;
+ u32 enable_streams;
- u64 tcr;
- u64 tcr_enabled;
- u64 tcr_disabled;
- u64 tcr_bypass;
+ u32 tcr;
+ u32 tcr_enabled;
+ u32 tcr_disabled;
+ u32 tcr_bypass;
+ u32 tcr_4level;
- u64 ttbr;
- u64 ttbr_valid;
- u64 ttbr_addr_field_shift;
- u64 ttbr_shift;
+ u32 ttbr;
+ u32 ttbr_valid;
+ u32 ttbr_addr_field_shift;
+ u32 ttbr_shift;
int ttbr_count;
};
@@ -196,6 +202,8 @@ struct apple_dart_hw {
* @lock: lock for hardware operations involving this dart
* @pgsize: pagesize supported by this DART
* @supports_bypass: indicates if this DART supports bypass mode
+ * @force_bypass: force bypass mode due to pagesize mismatch?
+ * @locked: indicates if this DART is locked
* @sid2group: maps stream ids to iommu_groups
* @iommu: iommu core device
*/
@@ -216,12 +224,21 @@ struct apple_dart {
u32 pgsize;
u32 num_streams;
u32 supports_bypass : 1;
+ u32 force_bypass : 1;
+ u32 locked : 1;
+ u32 four_level : 1;
+
+ dma_addr_t dma_min;
+ dma_addr_t dma_max;
struct iommu_group *sid2group[DART_MAX_STREAMS];
struct iommu_device iommu;
u32 save_tcr[DART_MAX_STREAMS];
u32 save_ttbr[DART_MAX_STREAMS][DART_MAX_TTBR];
+
+ u64 *locked_ttbr[DART_MAX_STREAMS][DART_MAX_TTBR];
+ u64 *shadow_ttbr[DART_MAX_STREAMS][DART_MAX_TTBR];
};
/*
@@ -261,6 +278,7 @@ struct apple_dart_domain {
struct io_pgtable_ops *pgtbl_ops;
bool finalized;
+ u64 mask;
struct mutex init_lock;
struct apple_dart_atomic_stream_map stream_maps[MAX_DARTS_PER_DEVICE];
@@ -301,13 +319,17 @@ static struct apple_dart_domain *to_dart_domain(struct iommu_domain *dom)
}
static void
-apple_dart_hw_enable_translation(struct apple_dart_stream_map *stream_map)
+apple_dart_hw_enable_translation(struct apple_dart_stream_map *stream_map, int levels)
{
struct apple_dart *dart = stream_map->dart;
int sid;
+ WARN_ON(levels != 3 && levels != 4);
+ WARN_ON(levels == 4 && !dart->four_level);
+ WARN_ON(stream_map->dart->locked);
for_each_set_bit(sid, stream_map->sidmap, dart->num_streams)
- writel(dart->hw->tcr_enabled, dart->regs + DART_TCR(dart, sid));
+ writel(dart->hw->tcr_enabled | (levels == 4 ? dart->hw->tcr_4level : 0),
+ dart->regs + DART_TCR(dart, sid));
}
static void apple_dart_hw_disable_dma(struct apple_dart_stream_map *stream_map)
@@ -315,6 +337,7 @@ static void apple_dart_hw_disable_dma(struct apple_dart_stream_map *stream_map)
struct apple_dart *dart = stream_map->dart;
int sid;
+ WARN_ON(stream_map->dart->locked);
for_each_set_bit(sid, stream_map->sidmap, dart->num_streams)
writel(dart->hw->tcr_disabled, dart->regs + DART_TCR(dart, sid));
}
@@ -325,6 +348,7 @@ apple_dart_hw_enable_bypass(struct apple_dart_stream_map *stream_map)
struct apple_dart *dart = stream_map->dart;
int sid;
+ WARN_ON(stream_map->dart->locked);
WARN_ON(!stream_map->dart->supports_bypass);
for_each_set_bit(sid, stream_map->sidmap, dart->num_streams)
writel(dart->hw->tcr_bypass,
@@ -337,6 +361,7 @@ static void apple_dart_hw_set_ttbr(struct apple_dart_stream_map *stream_map,
struct apple_dart *dart = stream_map->dart;
int sid;
+ WARN_ON(stream_map->dart->locked);
WARN_ON(paddr & ((1 << dart->hw->ttbr_shift) - 1));
for_each_set_bit(sid, stream_map->sidmap, dart->num_streams)
writel(dart->hw->ttbr_valid |
@@ -350,6 +375,7 @@ static void apple_dart_hw_clear_ttbr(struct apple_dart_stream_map *stream_map,
struct apple_dart *dart = stream_map->dart;
int sid;
+ WARN_ON(stream_map->dart->locked);
for_each_set_bit(sid, stream_map->sidmap, dart->num_streams)
writel(0, dart->regs + DART_TTBR(dart, sid, idx));
}
@@ -364,6 +390,89 @@ apple_dart_hw_clear_all_ttbrs(struct apple_dart_stream_map *stream_map)
}
static int
+apple_dart_hw_set_locked_ttbr(struct apple_dart_stream_map *stream_map, u8 idx,
+ phys_addr_t paddr)
+{
+ struct apple_dart *dart = stream_map->dart;
+ int sid;
+
+ WARN_ON(!dart->locked);
+ WARN_ON(paddr & ((1 << dart->hw->ttbr_shift) - 1));
+ for_each_set_bit(sid, stream_map->sidmap, dart->num_streams) {
+ u32 ttbr;
+ phys_addr_t phys;
+ u64 *l1_tbl, *l1_shadow;
+
+ ttbr = readl(dart->regs + DART_TTBR(dart, sid, idx));
+
+ WARN_ON(!(ttbr & dart->hw->ttbr_valid));
+ ttbr &= ~dart->hw->ttbr_valid;
+
+ if (dart->hw->ttbr_addr_field_shift)
+ ttbr >>= dart->hw->ttbr_addr_field_shift;
+ phys = ((phys_addr_t) ttbr) << dart->hw->ttbr_shift;
+
+ l1_tbl = devm_memremap(dart->dev, phys, dart->pgsize,
+ MEMREMAP_WB);
+ if (!l1_tbl)
+ return -ENOMEM;
+ l1_shadow = devm_memremap(dart->dev, paddr, dart->pgsize,
+ MEMREMAP_WB);
+ if (!l1_shadow)
+ return -ENOMEM;
+
+ dart->locked_ttbr[sid][idx] = l1_tbl;
+ dart->shadow_ttbr[sid][idx] = l1_shadow;
+ }
+
+ return 0;
+}
+
+static int
+apple_dart_hw_clear_locked_ttbr(struct apple_dart_stream_map *stream_map,
+ u8 idx)
+{
+ struct apple_dart *dart = stream_map->dart;
+ int sid;
+
+ WARN_ON(!dart->locked);
+ for_each_set_bit(sid, stream_map->sidmap, dart->num_streams) {
+ /* TODO: locked L1 table might need to be restored to boot state */
+ if (dart->locked_ttbr[sid][idx]) {
+ memset(dart->locked_ttbr[sid][idx], 0, dart->pgsize);
+ devm_memunmap(dart->dev, dart->locked_ttbr[sid][idx]);
+ }
+ dart->locked_ttbr[sid][idx] = NULL;
+ if (dart->shadow_ttbr[sid][idx])
+ devm_memunmap(dart->dev, dart->shadow_ttbr[sid][idx]);
+ dart->shadow_ttbr[sid][idx] = NULL;
+ }
+
+ return 0;
+}
+
+static int
+apple_dart_hw_sync_locked(struct apple_dart_stream_map *stream_map)
+{
+ struct apple_dart *dart = stream_map->dart;
+ int sid;
+
+ WARN_ON(!dart->locked);
+ for_each_set_bit(sid, stream_map->sidmap, dart->num_streams) {
+ for (int idx = 0; idx < dart->hw->ttbr_count; idx++) {
+ u64 *ttbrep = dart->locked_ttbr[sid][idx];
+ u64 *ptep = dart->shadow_ttbr[sid][idx];
+ if (!ttbrep || !ptep)
+ continue;
+ for (int entry = 0; entry < dart->pgsize / sizeof(*ptep); entry++)
+ ttbrep[entry] = ptep[entry];
+ }
+ }
+
+ return 0;
+}
+
+static int
apple_dart_t8020_hw_stream_command(struct apple_dart_stream_map *stream_map,
u32 command)
{
@@ -447,17 +556,9 @@ apple_dart_t8110_hw_invalidate_tlb(struct apple_dart_stream_map *stream_map)
static int apple_dart_hw_reset(struct apple_dart *dart)
{
- u32 config;
struct apple_dart_stream_map stream_map;
int i;
- config = readl(dart->regs + dart->hw->lock);
- if (config & dart->hw->lock_bit) {
- dev_err(dart->dev, "DART is locked down until reboot: %08x\n",
- config);
- return -EINVAL;
- }
-
stream_map.dart = dart;
bitmap_zero(stream_map.sidmap, DART_MAX_STREAMS);
bitmap_set(stream_map.sidmap, 0, dart->num_streams);
@@ -489,7 +590,13 @@ static void apple_dart_domain_flush_tlb(struct apple_dart_domain *domain)
for (j = 0; j < BITS_TO_LONGS(stream_map.dart->num_streams); j++)
stream_map.sidmap[j] = atomic_long_read(&domain_stream_map->sidmap[j]);
+ WARN_ON(pm_runtime_get_sync(stream_map.dart->dev) < 0);
+
+ if (stream_map.dart->locked)
+ apple_dart_hw_sync_locked(&stream_map);
+
stream_map.dart->hw->invalidate_tlb(&stream_map);
+ pm_runtime_put(stream_map.dart->dev);
}
}
@@ -520,7 +627,7 @@ static phys_addr_t apple_dart_iova_to_phys(struct iommu_domain *domain,
if (!ops)
return 0;
- return ops->iova_to_phys(ops, iova);
+ return ops->iova_to_phys(ops, iova & dart_domain->mask);
}
static int apple_dart_map_pages(struct iommu_domain *domain, unsigned long iova,
@@ -534,8 +641,8 @@ static int apple_dart_map_pages(struct iommu_domain *domain, unsigned long iova,
if (!ops)
return -ENODEV;
- return ops->map_pages(ops, iova, paddr, pgsize, pgcount, prot, gfp,
- mapped);
+ return ops->map_pages(ops, iova & dart_domain->mask, paddr, pgsize,
+ pgcount, prot, gfp, mapped);
}
static size_t apple_dart_unmap_pages(struct iommu_domain *domain,
@@ -546,7 +653,8 @@ static size_t apple_dart_unmap_pages(struct iommu_domain *domain,
struct apple_dart_domain *dart_domain = to_dart_domain(domain);
struct io_pgtable_ops *ops = dart_domain->pgtbl_ops;
- return ops->unmap_pages(ops, iova, pgsize, pgcount, gather);
+ return ops->unmap_pages(ops, iova & dart_domain->mask, pgsize, pgcount,
+ gather);
}
static void
@@ -557,21 +665,70 @@ apple_dart_setup_translation(struct apple_dart_domain *domain,
struct io_pgtable_cfg *pgtbl_cfg =
&io_pgtable_ops_to_pgtable(domain->pgtbl_ops)->cfg;
- for (i = 0; i < pgtbl_cfg->apple_dart_cfg.n_ttbrs; ++i)
- apple_dart_hw_set_ttbr(stream_map, i,
- pgtbl_cfg->apple_dart_cfg.ttbr[i]);
- for (; i < stream_map->dart->hw->ttbr_count; ++i)
- apple_dart_hw_clear_ttbr(stream_map, i);
+ /* Locked DARTs are set up by the bootloader. */
+ if (stream_map->dart->locked) {
+ for (i = 0; i < pgtbl_cfg->apple_dart_cfg.n_ttbrs; ++i)
+ apple_dart_hw_set_locked_ttbr(stream_map, i,
+ pgtbl_cfg->apple_dart_cfg.ttbr[i]);
+ for (; i < stream_map->dart->hw->ttbr_count; ++i)
+ apple_dart_hw_clear_locked_ttbr(stream_map, i);
+ apple_dart_hw_sync_locked(stream_map);
+ } else {
+ for (i = 0; i < pgtbl_cfg->apple_dart_cfg.n_ttbrs; ++i)
+ apple_dart_hw_set_ttbr(stream_map, i,
+ pgtbl_cfg->apple_dart_cfg.ttbr[i]);
+ for (; i < stream_map->dart->hw->ttbr_count; ++i)
+ apple_dart_hw_clear_ttbr(stream_map, i);
- apple_dart_hw_enable_translation(stream_map);
+ apple_dart_hw_enable_translation(stream_map,
+ pgtbl_cfg->apple_dart_cfg.n_levels);
+ }
stream_map->dart->hw->invalidate_tlb(stream_map);
}
-static int apple_dart_finalize_domain(struct apple_dart_domain *dart_domain,
+static int apple_dart_setup_resv_locked(struct iommu_domain *domain,
+ struct device *dev, size_t pgsize)
+{
+ struct iommu_resv_region *region;
+ LIST_HEAD(resv_regions);
+ int ret = 0;
+
+ of_iommu_get_resv_regions(dev, &resv_regions);
+ list_for_each_entry(region, &resv_regions, list) {
+ size_t mapped = 0;
+
+ /* Only map translated reserved regions */
+ if (region->type != IOMMU_RESV_TRANSLATED)
+ continue;
+
+ while (mapped < region->length) {
+ phys_addr_t paddr = region->start + mapped;
+ unsigned long iova = region->dva + mapped;
+ size_t length = region->length - mapped;
+ size_t pgcount = length / pgsize;
+
+ ret = apple_dart_map_pages(domain, iova,
+ paddr, pgsize, pgcount,
+ region->prot, GFP_KERNEL, &mapped);
+
+ if (ret)
+ goto end_put;
+ }
+ }
+end_put:
+ iommu_put_resv_regions(dev, &resv_regions);
+ return ret;
+}
+
+static int apple_dart_finalize_domain(struct iommu_domain *domain,
+ struct device *dev,
struct apple_dart_master_cfg *cfg)
{
+ struct apple_dart_domain *dart_domain = to_dart_domain(domain);
struct apple_dart *dart = cfg->stream_maps[0].dart;
struct io_pgtable_cfg pgtbl_cfg;
+ dma_addr_t dma_max = dart->dma_max;
+ u32 ias = min_t(u32, dart->ias, fls64(dma_max));
int ret = 0;
int i, j;
@@ -592,27 +749,70 @@ static int apple_dart_finalize_domain(struct apple_dart_domain *dart_domain,
pgtbl_cfg = (struct io_pgtable_cfg){
.pgsize_bitmap = dart->pgsize,
- .ias = dart->ias,
+ .ias = ias,
.oas = dart->oas,
.coherent_walk = 1,
.iommu_dev = dart->dev,
};
- dart_domain->pgtbl_ops = alloc_io_pgtable_ops(dart->hw->fmt, &pgtbl_cfg,
- &dart_domain->domain);
+ if (dart->locked) {
+ unsigned long *sidmap;
+ int sid;
+ u32 ttbr;
+
+ /* Locked DARTs can only have a single stream bound */
+ sidmap = cfg->stream_maps[0].sidmap;
+ sid = find_first_bit(sidmap, dart->num_streams);
+
+ WARN_ON((sid < 0) || bitmap_weight(sidmap, dart->num_streams) > 1);
+ ttbr = readl(dart->regs + DART_TTBR(dart, sid, 0));
+
+ WARN_ON(!(ttbr & dart->hw->ttbr_valid));
+
+ /* If the DART is locked, we need to keep the translation level count. */
+ if (dart->hw->tcr_4level && dart->ias > 36) {
+ if (readl(dart->regs + DART_TCR(dart, sid)) & dart->hw->tcr_4level) {
+ if (ias < 37) {
+ dev_info(dart->dev, "Expanded to ias=37 due to lock\n");
+ pgtbl_cfg.ias = 37;
+ }
+ } else if (ias > 36) {
+ dev_info(dart->dev, "Limited to ias=36 due to lock\n");
+ pgtbl_cfg.ias = 36;
+ if (dart->dma_min == 0 && dma_max == DMA_BIT_MASK(dart->ias)) {
+ dma_max = DMA_BIT_MASK(pgtbl_cfg.ias);
+ } else if ((dart->dma_min ^ dma_max) & ~DMA_BIT_MASK(36)) {
+ dev_err(dart->dev,
+ "Invalid DMA range for locked 3-level PT\n");
+ ret = -ENOMEM;
+ goto done;
+ }
+ }
+ }
+ }
+
+ dart_domain->pgtbl_ops =
+ alloc_io_pgtable_ops(dart->hw->fmt, &pgtbl_cfg, domain);
if (!dart_domain->pgtbl_ops) {
ret = -ENOMEM;
goto done;
}
- dart_domain->domain.pgsize_bitmap = pgtbl_cfg.pgsize_bitmap;
- dart_domain->domain.geometry.aperture_start = 0;
- dart_domain->domain.geometry.aperture_end =
- (dma_addr_t)DMA_BIT_MASK(dart->ias);
- dart_domain->domain.geometry.force_aperture = true;
+ if (pgtbl_cfg.pgsize_bitmap == SZ_4K)
+ dart_domain->mask = DMA_BIT_MASK(min_t(u32, dart->ias, 32));
+ else if (pgtbl_cfg.apple_dart_cfg.n_levels == 3)
+ dart_domain->mask = DMA_BIT_MASK(min_t(u32, dart->ias, 36));
+ else if (pgtbl_cfg.apple_dart_cfg.n_levels == 4)
+ dart_domain->mask = DMA_BIT_MASK(min_t(u32, dart->ias, 47));
+
+ domain->pgsize_bitmap = pgtbl_cfg.pgsize_bitmap;
+ domain->geometry.aperture_start = dart->dma_min;
+ domain->geometry.aperture_end = dma_max;
+ domain->geometry.force_aperture = true;
dart_domain->finalized = true;
+ ret = apple_dart_setup_resv_locked(domain, dev, dart->pgsize);
done:
mutex_unlock(&dart_domain->init_lock);
return ret;
@@ -660,18 +860,47 @@ static int apple_dart_attach_dev_paging(struct iommu_domain *domain,
struct apple_dart_stream_map *stream_map;
struct apple_dart_master_cfg *cfg = dev_iommu_priv_get(dev);
struct apple_dart_domain *dart_domain = to_dart_domain(domain);
+ struct apple_dart *dart0 = cfg->stream_maps[0].dart;
- ret = apple_dart_finalize_domain(dart_domain, cfg);
- if (ret)
- return ret;
-
- ret = apple_dart_domain_add_streams(dart_domain, cfg);
- if (ret)
- return ret;
+ if (dart0->force_bypass && domain->type != IOMMU_DOMAIN_IDENTITY)
+ return -EINVAL;
+ if (!dart0->supports_bypass && domain->type == IOMMU_DOMAIN_IDENTITY)
+ return -EINVAL;
+ if (dart0->locked && domain->type != IOMMU_DOMAIN_DMA &&
+ domain->type != IOMMU_DOMAIN_UNMANAGED)
+ return -EINVAL;
for_each_stream_map(i, cfg, stream_map)
- apple_dart_setup_translation(dart_domain, stream_map);
- return 0;
+ WARN_ON(pm_runtime_get_sync(stream_map->dart->dev) < 0);
+
+ ret = apple_dart_finalize_domain(domain, dev, cfg);
+ if (ret)
+ goto err;
+
+ switch (domain->type) {
+ case IOMMU_DOMAIN_DMA:
+ case IOMMU_DOMAIN_UNMANAGED:
+ ret = apple_dart_domain_add_streams(dart_domain, cfg);
+ if (ret)
+ goto err;
+
+ for_each_stream_map(i, cfg, stream_map)
+ apple_dart_setup_translation(dart_domain, stream_map);
+ break;
+ case IOMMU_DOMAIN_BLOCKED:
+ for_each_stream_map(i, cfg, stream_map)
+ apple_dart_hw_disable_dma(stream_map);
+ break;
+ case IOMMU_DOMAIN_IDENTITY:
+ for_each_stream_map(i, cfg, stream_map)
+ apple_dart_hw_enable_bypass(stream_map);
+ break;
+ }
+
+err:
+ for_each_stream_map(i, cfg, stream_map)
+ pm_runtime_put(stream_map->dart->dev);
+ return ret;
}
static int apple_dart_attach_dev_identity(struct iommu_domain *domain,
@@ -729,17 +958,25 @@ static struct iommu_device *apple_dart_probe_device(struct device *dev)
return ERR_PTR(-ENODEV);
for_each_stream_map(i, cfg, stream_map)
- device_link_add(
- dev, stream_map->dart->dev,
- DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_SUPPLIER);
+ device_link_add(dev, stream_map->dart->dev,
+ DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_SUPPLIER |
+ DL_FLAG_RPM_ACTIVE);
return &cfg->stream_maps[0].dart->iommu;
}
static void apple_dart_release_device(struct device *dev)
{
+ int i, j;
+ struct apple_dart_stream_map *stream_map;
struct apple_dart_master_cfg *cfg = dev_iommu_priv_get(dev);
+ for_each_stream_map(j, cfg, stream_map) {
+ if (stream_map->dart->locked)
+ for (i = 0; i < stream_map->dart->hw->ttbr_count; ++i)
+ apple_dart_hw_clear_locked_ttbr(stream_map, i);
+ }
+
dev_iommu_priv_set(dev, NULL);
kfree(cfg);
}
@@ -758,7 +995,7 @@ static struct iommu_domain *apple_dart_domain_alloc_paging(struct device *dev)
struct apple_dart_master_cfg *cfg = dev_iommu_priv_get(dev);
int ret;
- ret = apple_dart_finalize_domain(dart_domain, cfg);
+ ret = apple_dart_finalize_domain(&dart_domain->domain, dev, cfg);
if (ret) {
kfree(dart_domain);
return ERR_PTR(ret);
@@ -939,10 +1176,15 @@ static struct iommu_group *apple_dart_device_group(struct device *dev)
static int apple_dart_def_domain_type(struct device *dev)
{
struct apple_dart_master_cfg *cfg = dev_iommu_priv_get(dev);
+ struct apple_dart *dart = cfg->stream_maps[0].dart;
- if (cfg->stream_maps[0].dart->pgsize > PAGE_SIZE)
+ WARN_ON(dart->force_bypass && dart->locked);
+
+ if (dart->force_bypass)
return IOMMU_DOMAIN_IDENTITY;
- if (!cfg->stream_maps[0].dart->supports_bypass)
+ if (dart->locked)
+ return IOMMU_DOMAIN_DMA;
+ if (dart->supports_bypass)
return IOMMU_DOMAIN_DMA;
return 0;
@@ -1044,6 +1286,7 @@ static irqreturn_t apple_dart_t8110_irq(int irq, void *dev)
u32 addr_hi = readl(dart->regs + DART_T8110_ERROR_ADDR_HI);
u64 addr = addr_lo | (((u64)addr_hi) << 32);
u8 stream_idx = FIELD_GET(DART_T8110_ERROR_STREAM, error);
+ int i;
if (!(error & DART_T8110_ERROR_FLAG))
return IRQ_NONE;
@@ -1070,9 +1313,17 @@ static irqreturn_t apple_dart_t8110_irq(int irq, void *dev)
error, stream_idx, error_code, fault_name, addr);
writel(error, dart->regs + DART_T8110_ERROR);
+ for (i = 0; i < BITS_TO_U32(dart->num_streams); i++)
+ writel(U32_MAX, dart->regs + DART_T8110_ERROR_STREAMS + 4 * i);
+
return IRQ_HANDLED;
}
+static bool apple_dart_is_locked(struct apple_dart *dart)
+{
+ return !!(readl(dart->regs + dart->hw->lock) & dart->hw->lock_bit);
+}
+
static int apple_dart_probe(struct platform_device *pdev)
{
int ret;
@@ -1080,6 +1331,7 @@ static int apple_dart_probe(struct platform_device *pdev)
struct resource *res;
struct apple_dart *dart;
struct device *dev = &pdev->dev;
+ u64 dma_range[2];
dart = devm_kzalloc(dev, sizeof(*dart), GFP_KERNEL);
if (!dart)
@@ -1111,6 +1363,14 @@ static int apple_dart_probe(struct platform_device *pdev)
if (ret)
return ret;
+ pm_runtime_get_noresume(dev);
+ pm_runtime_set_active(dev);
+ pm_runtime_irq_safe(dev);
+
+ ret = devm_pm_runtime_enable(dev);
+ if (ret)
+ goto err_clk_disable;
+
dart_params[0] = readl(dart->regs + DART_PARAMS1);
dart_params[1] = readl(dart->regs + DART_PARAMS2);
dart->pgsize = 1 << FIELD_GET(DART_PARAMS1_PAGE_SHIFT, dart_params[0]);
@@ -1130,9 +1390,30 @@ static int apple_dart_probe(struct platform_device *pdev)
dart->ias = FIELD_GET(DART_T8110_PARAMS3_VA_WIDTH, dart_params[2]);
dart->oas = FIELD_GET(DART_T8110_PARAMS3_PA_WIDTH, dart_params[2]);
dart->num_streams = FIELD_GET(DART_T8110_PARAMS4_NUM_SIDS, dart_params[3]);
+ dart->four_level = dart->ias > 36;
break;
}
+ dart->dma_min = 0;
+ dart->dma_max = DMA_BIT_MASK(dart->ias);
+
+ ret = of_property_read_u64_array(dev->of_node, "apple,dma-range", dma_range, 2);
+ if (ret == -EINVAL) {
+ ret = 0;
+ } else if (ret) {
+ goto err_clk_disable;
+ } else {
+ dart->dma_min = dma_range[0];
+ dart->dma_max = dma_range[0] + dma_range[1] - 1;
+ if ((dart->dma_min ^ dart->dma_max) & ~DMA_BIT_MASK(dart->ias)) {
+ dev_err(&pdev->dev, "Invalid DMA range for ias=%d\n",
+ dart->ias);
+ goto err_clk_disable;
+ }
+ dev_info(&pdev->dev, "Limiting DMA range to %pad..%pad\n",
+ &dart->dma_min, &dart->dma_max);
+ }
+
if (dart->num_streams > DART_MAX_STREAMS) {
dev_err(&pdev->dev, "Too many streams (%d > %d)\n",
dart->num_streams, DART_MAX_STREAMS);
@@ -1140,9 +1421,14 @@ static int apple_dart_probe(struct platform_device *pdev)
goto err_clk_disable;
}
- ret = apple_dart_hw_reset(dart);
- if (ret)
- goto err_clk_disable;
+ dart->force_bypass = dart->pgsize > PAGE_SIZE;
+
+ dart->locked = apple_dart_is_locked(dart);
+ if (!dart->locked) {
+ ret = apple_dart_hw_reset(dart);
+ if (ret)
+ goto err_clk_disable;
+ }
ret = request_irq(dart->irq, dart->hw->irq_handler, IRQF_SHARED,
"apple-dart fault handler", dart);
@@ -1160,11 +1446,13 @@ static int apple_dart_probe(struct platform_device *pdev)
if (ret)
goto err_sysfs_remove;
+ pm_runtime_put(dev);
+
dev_info(
&pdev->dev,
- "DART [pagesize %x, %d streams, bypass support: %d, bypass forced: %d] initialized\n",
- dart->pgsize, dart->num_streams, dart->supports_bypass,
- dart->pgsize > PAGE_SIZE);
+ "DART [pagesize %x, %d streams, bypass support: %d, bypass forced: %d, locked: %d, AS %d -> %d] initialized\n",
+ dart->pgsize, dart->num_streams, dart->supports_bypass, dart->force_bypass, dart->locked,
+ dart->ias, dart->oas);
return 0;
err_sysfs_remove:
@@ -1172,6 +1460,7 @@ static int apple_dart_probe(struct platform_device *pdev)
err_free_irq:
free_irq(dart->irq, dart);
err_clk_disable:
+ pm_runtime_put(dev);
clk_bulk_disable_unprepare(dart->num_clks, dart->clks);
return ret;
@@ -1181,7 +1470,9 @@ static void apple_dart_remove(struct platform_device *pdev)
{
struct apple_dart *dart = platform_get_drvdata(pdev);
- apple_dart_hw_reset(dart);
+ if (!dart->locked)
+ apple_dart_hw_reset(dart);
+
free_irq(dart->irq, dart);
iommu_device_unregister(&dart->iommu);
@@ -1258,6 +1549,7 @@ static const struct apple_dart_hw apple_dart_hw_t8110 = {
.tcr_enabled = DART_T8110_TCR_TRANSLATE_ENABLE,
.tcr_disabled = 0,
.tcr_bypass = DART_T8110_TCR_BYPASS_DAPF | DART_T8110_TCR_BYPASS_DART,
+ .tcr_4level = DART_T8110_TCR_FOUR_LEVEL,
.ttbr = DART_T8110_TTBR,
.ttbr_valid = DART_T8110_TTBR_VALID,
@@ -1287,6 +1579,10 @@ static __maybe_unused int apple_dart_resume(struct device *dev)
unsigned int sid, idx;
int ret;
+ /* Locked DARTs can't be restored, and they should not need it */
+ if (dart->locked)
+ return 0;
+
ret = apple_dart_hw_reset(dart);
if (ret) {
dev_err(dev, "Failed to reset DART on resume\n");
@@ -1303,7 +1599,7 @@ static __maybe_unused int apple_dart_resume(struct device *dev)
return 0;
}
-static DEFINE_SIMPLE_DEV_PM_OPS(apple_dart_pm_ops, apple_dart_suspend, apple_dart_resume);
+DEFINE_RUNTIME_DEV_PM_OPS(apple_dart_pm_ops, apple_dart_suspend, apple_dart_resume, NULL);
static const struct of_device_id apple_dart_of_match[] = {
{ .compatible = "apple,t8103-dart", .data = &apple_dart_hw_t8103 },
@@ -1318,7 +1614,7 @@ static struct platform_driver apple_dart_driver = {
.name = "apple-dart",
.of_match_table = apple_dart_of_match,
.suppress_bind_attrs = true,
- .pm = pm_sleep_ptr(&apple_dart_pm_ops),
+ .pm = pm_ptr(&apple_dart_pm_ops),
},
.probe = apple_dart_probe,
.remove_new = apple_dart_remove,
diff --git a/drivers/iommu/dma-iommu.c b/drivers/iommu/dma-iommu.c
index 85163a8..c4356bb 100644
--- a/drivers/iommu/dma-iommu.c
+++ b/drivers/iommu/dma-iommu.c
@@ -580,8 +580,13 @@ static int iova_reserve_iommu_regions(struct device *dev,
if (region->type == IOMMU_RESV_SW_MSI)
continue;
- lo = iova_pfn(iovad, region->start);
- hi = iova_pfn(iovad, region->start + region->length - 1);
+ if (region->type == IOMMU_RESV_TRANSLATED) {
+ lo = iova_pfn(iovad, region->dva);
+ hi = iova_pfn(iovad, region->dva + region->length - 1);
+ } else {
+ lo = iova_pfn(iovad, region->start);
+ hi = iova_pfn(iovad, region->start + region->length - 1);
+ }
reserve_iova(iovad, lo, hi);
if (region->type == IOMMU_RESV_MSI)
diff --git a/drivers/iommu/io-pgtable-dart.c b/drivers/iommu/io-pgtable-dart.c
index 74b1ef2..8998705 100644
--- a/drivers/iommu/io-pgtable-dart.c
+++ b/drivers/iommu/io-pgtable-dart.c
@@ -26,8 +26,9 @@
#define DART1_MAX_ADDR_BITS 36
-#define DART_MAX_TABLES 4
-#define DART_LEVELS 2
+#define DART_MAX_TABLE_BITS 2
+#define DART_MAX_TABLES BIT(DART_MAX_TABLE_BITS)
+#define DART_MAX_LEVELS 4 /* Includes TTBR level */
/* Struct accessors */
#define io_pgtable_to_data(x) \
@@ -67,6 +68,7 @@
struct dart_io_pgtable {
struct io_pgtable iop;
+ int levels;
int tbl_bits;
int bits_per_level;
@@ -106,8 +108,7 @@ static phys_addr_t iopte_to_paddr(dart_iopte pte,
return paddr;
}
-static void *__dart_alloc_pages(size_t size, gfp_t gfp,
- struct io_pgtable_cfg *cfg)
+static void *__dart_alloc_pages(size_t size, gfp_t gfp)
{
int order = get_order(size);
struct page *p;
@@ -170,44 +171,45 @@ static dart_iopte dart_install_table(dart_iopte *table,
return old;
}
-static int dart_get_table(struct dart_io_pgtable *data, unsigned long iova)
+static int dart_get_index(struct dart_io_pgtable *data, unsigned long iova, int level)
{
- return (iova >> (3 * data->bits_per_level + ilog2(sizeof(dart_iopte)))) &
- ((1 << data->tbl_bits) - 1);
+ return (iova >> (level * data->bits_per_level + ilog2(sizeof(dart_iopte)))) &
+ ((1 << data->bits_per_level) - 1);
}
-static int dart_get_l1_index(struct dart_io_pgtable *data, unsigned long iova)
-{
-
- return (iova >> (2 * data->bits_per_level + ilog2(sizeof(dart_iopte)))) &
- ((1 << data->bits_per_level) - 1);
-}
-
-static int dart_get_l2_index(struct dart_io_pgtable *data, unsigned long iova)
+static int dart_get_last_index(struct dart_io_pgtable *data, unsigned long iova)
{
return (iova >> (data->bits_per_level + ilog2(sizeof(dart_iopte)))) &
((1 << data->bits_per_level) - 1);
}
-static dart_iopte *dart_get_l2(struct dart_io_pgtable *data, unsigned long iova)
+static dart_iopte *dart_get_last(struct dart_io_pgtable *data, unsigned long iova)
{
dart_iopte pte, *ptep;
- int tbl = dart_get_table(data, iova);
+ int level = data->levels;
+ int tbl = dart_get_index(data, iova, level);
+
+ if (tbl > (1 << data->tbl_bits))
+ return NULL;
ptep = data->pgd[tbl];
if (!ptep)
return NULL;
- ptep += dart_get_l1_index(data, iova);
- pte = READ_ONCE(*ptep);
+ while (--level > 1) {
+ ptep += dart_get_index(data, iova, level);
+ pte = READ_ONCE(*ptep);
- /* Valid entry? */
- if (!pte)
- return NULL;
+ /* Valid entry? */
+ if (!pte)
+ return NULL;
- /* Deref to get level 2 table */
- return iopte_deref(pte, data);
+ /* Deref to get next level table */
+ ptep = iopte_deref(pte, data);
+ }
+
+ return ptep;
}
static dart_iopte dart_prot_to_pte(struct dart_io_pgtable *data,
@@ -243,6 +245,7 @@ static int dart_map_pages(struct io_pgtable_ops *ops, unsigned long iova,
int ret = 0, tbl, num_entries, max_entries, map_idx_start;
dart_iopte pte, *cptep, *ptep;
dart_iopte prot;
+ int level = data->levels;
if (WARN_ON(pgsize != cfg->pgsize_bitmap))
return -EINVAL;
@@ -254,31 +257,36 @@ static int dart_map_pages(struct io_pgtable_ops *ops, unsigned long iova,
if (!(iommu_prot & (IOMMU_READ | IOMMU_WRITE)))
return 0;
- tbl = dart_get_table(data, iova);
+ tbl = dart_get_index(data, iova, level);
+
+ if (tbl > (1 << data->tbl_bits))
+ return -ENOMEM;
ptep = data->pgd[tbl];
- ptep += dart_get_l1_index(data, iova);
- pte = READ_ONCE(*ptep);
-
- /* no L2 table present */
- if (!pte) {
- cptep = __dart_alloc_pages(tblsz, gfp, cfg);
- if (!cptep)
- return -ENOMEM;
-
- pte = dart_install_table(cptep, ptep, 0, data);
- if (pte)
- free_pages((unsigned long)cptep, get_order(tblsz));
-
- /* L2 table is present (now) */
+ while (--level > 1) {
+ ptep += dart_get_index(data, iova, level);
pte = READ_ONCE(*ptep);
- }
- ptep = iopte_deref(pte, data);
+ /* no table present */
+ if (!pte) {
+ cptep = __dart_alloc_pages(tblsz, gfp);
+ if (!cptep)
+ return -ENOMEM;
+
+ pte = dart_install_table(cptep, ptep, 0, data);
+ if (pte)
+ free_pages((unsigned long)cptep, get_order(tblsz));
+
+ /* L2 table is present (now) */
+ pte = READ_ONCE(*ptep);
+ }
+
+ ptep = iopte_deref(pte, data);
+ }
/* install a leaf entries into L2 table */
prot = dart_prot_to_pte(data, iommu_prot);
- map_idx_start = dart_get_l2_index(data, iova);
+ map_idx_start = dart_get_last_index(data, iova);
max_entries = DART_PTES_PER_TABLE(data) - map_idx_start;
num_entries = min_t(int, pgcount, max_entries);
ptep += map_idx_start;
@@ -307,13 +315,13 @@ static size_t dart_unmap_pages(struct io_pgtable_ops *ops, unsigned long iova,
if (WARN_ON(pgsize != cfg->pgsize_bitmap || !pgcount))
return 0;
- ptep = dart_get_l2(data, iova);
+ ptep = dart_get_last(data, iova);
/* Valid L2 IOPTE pointer? */
if (WARN_ON(!ptep))
return 0;
- unmap_idx_start = dart_get_l2_index(data, iova);
+ unmap_idx_start = dart_get_last_index(data, iova);
ptep += unmap_idx_start;
max_entries = DART_PTES_PER_TABLE(data) - unmap_idx_start;
@@ -344,13 +352,13 @@ static phys_addr_t dart_iova_to_phys(struct io_pgtable_ops *ops,
struct dart_io_pgtable *data = io_pgtable_ops_to_data(ops);
dart_iopte pte, *ptep;
- ptep = dart_get_l2(data, iova);
+ ptep = dart_get_last(data, iova);
/* Valid L2 IOPTE pointer? */
if (!ptep)
return 0;
- ptep += dart_get_l2_index(data, iova);
+ ptep += dart_get_last_index(data, iova);
pte = READ_ONCE(*ptep);
/* Found translation */
@@ -367,21 +375,37 @@ static struct dart_io_pgtable *
dart_alloc_pgtable(struct io_pgtable_cfg *cfg)
{
struct dart_io_pgtable *data;
- int tbl_bits, bits_per_level, va_bits, pg_shift;
+ int levels, max_tbl_bits, tbl_bits, bits_per_level, va_bits, pg_shift;
+
+ /*
+ * Old 4K page DARTs can use up to 4 top-level tables.
+ * Newer ones only ever use a maximum of 1.
+ */
+ if (cfg->pgsize_bitmap == SZ_4K)
+ max_tbl_bits = DART_MAX_TABLE_BITS;
+ else
+ max_tbl_bits = 0;
pg_shift = __ffs(cfg->pgsize_bitmap);
bits_per_level = pg_shift - ilog2(sizeof(dart_iopte));
va_bits = cfg->ias - pg_shift;
- tbl_bits = max_t(int, 0, va_bits - (bits_per_level * DART_LEVELS));
- if ((1 << tbl_bits) > DART_MAX_TABLES)
+ levels = max_t(int, 2, (va_bits - max_tbl_bits + bits_per_level - 1) / bits_per_level);
+
+ if (levels > (DART_MAX_LEVELS - 1))
+ return NULL;
+
+ tbl_bits = max_t(int, 0, va_bits - (bits_per_level * levels));
+
+ if (tbl_bits > max_tbl_bits)
return NULL;
data = kzalloc(sizeof(*data), GFP_KERNEL);
if (!data)
return NULL;
+ data->levels = levels + 1; /* Table level counts as one level */
data->tbl_bits = tbl_bits;
data->bits_per_level = bits_per_level;
@@ -417,10 +441,10 @@ apple_dart_alloc_pgtable(struct io_pgtable_cfg *cfg, void *cookie)
return NULL;
cfg->apple_dart_cfg.n_ttbrs = 1 << data->tbl_bits;
+ cfg->apple_dart_cfg.n_levels = data->levels;
for (i = 0; i < cfg->apple_dart_cfg.n_ttbrs; ++i) {
- data->pgd[i] = __dart_alloc_pages(DART_GRANULE(data), GFP_KERNEL,
- cfg);
+ data->pgd[i] = __dart_alloc_pages(DART_GRANULE(data), GFP_KERNEL);
if (!data->pgd[i])
goto out_free_data;
cfg->apple_dart_cfg.ttbr[i] = virt_to_phys(data->pgd[i]);
@@ -436,28 +460,32 @@ apple_dart_alloc_pgtable(struct io_pgtable_cfg *cfg, void *cookie)
return NULL;
}
-static void apple_dart_free_pgtable(struct io_pgtable *iop)
+static void apple_dart_free_pgtables(struct dart_io_pgtable *data, dart_iopte *ptep, int level)
{
- struct dart_io_pgtable *data = io_pgtable_to_data(iop);
- dart_iopte *ptep, *end;
- int i;
+ dart_iopte *end;
- for (i = 0; i < (1 << data->tbl_bits) && data->pgd[i]; ++i) {
- ptep = data->pgd[i];
+ if (level > 1) {
end = (void *)ptep + DART_GRANULE(data);
while (ptep != end) {
dart_iopte pte = *ptep++;
- if (pte) {
- unsigned long page =
- (unsigned long)iopte_deref(pte, data);
-
- free_pages(page, get_order(DART_GRANULE(data)));
- }
+ if (pte)
+ apple_dart_free_pgtables(data, iopte_deref(pte, data), level - 1);
}
- free_pages((unsigned long)data->pgd[i],
- get_order(DART_GRANULE(data)));
+ }
+ free_pages((unsigned long)ptep, get_order(DART_GRANULE(data)));
+}
+
+static void apple_dart_free_pgtable(struct io_pgtable *iop)
+{
+ struct dart_io_pgtable *data = io_pgtable_to_data(iop);
+ dart_iopte *ptep;
+ int i;
+
+ for (i = 0; i < (1 << data->tbl_bits) && data->pgd[i]; ++i) {
+ ptep = data->pgd[i];
+ apple_dart_free_pgtables(data, data->pgd[i], data->levels - 1);
}
kfree(data);
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index f17a111..970108d 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -87,6 +87,7 @@ static const char * const iommu_group_resv_type_string[] = {
[IOMMU_RESV_RESERVED] = "reserved",
[IOMMU_RESV_MSI] = "msi",
[IOMMU_RESV_SW_MSI] = "msi",
+ [IOMMU_RESV_TRANSLATED] = "translated",
};
#define IOMMU_CMD_LINE_DMA_API BIT(0)
@@ -2170,6 +2171,15 @@ static int __iommu_attach_device(struct iommu_domain *domain,
if (unlikely(domain->ops->attach_dev == NULL))
return -ENODEV;
+ /*
+ * Ensure we do not try to attach devices to FQ domains if the
+ * IOMMU does not support them. We can safely fall back to
+ * non-FQ.
+ */
+ if (domain->type == IOMMU_DOMAIN_DMA_FQ &&
+ !device_iommu_capable(dev, IOMMU_CAP_DEFERRED_FLUSH))
+ domain->type = IOMMU_DOMAIN_DMA;
+
ret = domain->ops->attach_dev(domain, dev);
if (ret)
return ret;
@@ -2865,10 +2875,11 @@ void iommu_put_resv_regions(struct device *dev, struct list_head *list)
}
EXPORT_SYMBOL(iommu_put_resv_regions);
-struct iommu_resv_region *iommu_alloc_resv_region(phys_addr_t start,
- size_t length, int prot,
- enum iommu_resv_type type,
- gfp_t gfp)
+struct iommu_resv_region *iommu_alloc_resv_region_tr(phys_addr_t start,
+ dma_addr_t dva_start,
+ size_t length, int prot,
+ enum iommu_resv_type type,
+ gfp_t gfp)
{
struct iommu_resv_region *region;
@@ -2878,11 +2889,25 @@ struct iommu_resv_region *iommu_alloc_resv_region(phys_addr_t start,
INIT_LIST_HEAD(®ion->list);
region->start = start;
+ if (type == IOMMU_RESV_TRANSLATED)
+ region->dva = dva_start;
region->length = length;
region->prot = prot;
region->type = type;
return region;
}
+EXPORT_SYMBOL_GPL(iommu_alloc_resv_region_tr);
+
+struct iommu_resv_region *iommu_alloc_resv_region(phys_addr_t start,
+ size_t length, int prot,
+ enum iommu_resv_type type,
+ gfp_t gfp)
+{
+ if (type == IOMMU_RESV_TRANSLATED)
+ return NULL;
+
+ return iommu_alloc_resv_region_tr(start, 0, length, prot, type, gfp);
+}
EXPORT_SYMBOL_GPL(iommu_alloc_resv_region);
void iommu_set_default_passthrough(bool cmd_line)
diff --git a/drivers/iommu/of_iommu.c b/drivers/iommu/of_iommu.c
index 157b286..5f35943 100644
--- a/drivers/iommu/of_iommu.c
+++ b/drivers/iommu/of_iommu.c
@@ -191,9 +191,7 @@ iommu_resv_region_get_type(struct device *dev,
if (start == phys->start && end == phys->end)
return IOMMU_RESV_DIRECT;
- dev_warn(dev, "treating non-direct mapping [%pr] -> [%pap-%pap] as reservation\n", &phys,
- &start, &end);
- return IOMMU_RESV_RESERVED;
+ return IOMMU_RESV_TRANSLATED;
}
/**
@@ -257,8 +255,13 @@ void of_iommu_get_resv_regions(struct device *dev, struct list_head *list)
maps = of_translate_dma_region(np, maps, &iova, &length);
type = iommu_resv_region_get_type(dev, &phys, iova, length);
- region = iommu_alloc_resv_region(iova, length, prot, type,
+ if (type == IOMMU_RESV_TRANSLATED)
+ region = iommu_alloc_resv_region_tr(phys.start, iova, length, prot, type,
+ GFP_KERNEL);
+ else
+ region = iommu_alloc_resv_region(iova, length, prot, type,
GFP_KERNEL);
+
if (region)
list_add_tail(®ion->list, list);
}
diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig
index bc2e265..4294010 100644
--- a/drivers/mailbox/Kconfig
+++ b/drivers/mailbox/Kconfig
@@ -8,18 +8,6 @@
if MAILBOX
-config APPLE_MAILBOX
- tristate "Apple Mailbox driver"
- depends on ARCH_APPLE || (ARM64 && COMPILE_TEST)
- default ARCH_APPLE
- help
- Apple SoCs have various co-processors required for certain
- peripherals to work (NVMe, display controller, etc.). This
- driver adds support for the mailbox controller used to
- communicate with those.
-
- Say Y here if you have a Apple SoC.
-
config ARM_MHU
tristate "ARM MHU Mailbox"
depends on ARM_AMBA
diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile
index fc93761..18793e6 100644
--- a/drivers/mailbox/Makefile
+++ b/drivers/mailbox/Makefile
@@ -60,5 +60,3 @@
obj-$(CONFIG_SPRD_MBOX) += sprd-mailbox.o
obj-$(CONFIG_QCOM_IPCC) += qcom-ipcc.o
-
-obj-$(CONFIG_APPLE_MAILBOX) += apple-mailbox.o
diff --git a/drivers/mailbox/apple-mailbox.c b/drivers/mailbox/apple-mailbox.c
deleted file mode 100644
index 2a3e8d8..0000000
--- a/drivers/mailbox/apple-mailbox.c
+++ /dev/null
@@ -1,441 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only OR MIT
-/*
- * Apple mailbox driver
- *
- * Copyright (C) 2021 The Asahi Linux Contributors
- *
- * This driver adds support for two mailbox variants (called ASC and M3 by
- * Apple) found in Apple SoCs such as the M1. It consists of two FIFOs used to
- * exchange 64+32 bit messages between the main CPU and a co-processor.
- * Various coprocessors implement different IPC protocols based on these simple
- * messages and shared memory buffers.
- *
- * Both the main CPU and the co-processor see the same set of registers but
- * the first FIFO (A2I) is always used to transfer messages from the application
- * processor (us) to the I/O processor and the second one (I2A) for the
- * other direction.
- */
-
-#include <linux/apple-mailbox.h>
-#include <linux/delay.h>
-#include <linux/device.h>
-#include <linux/gfp.h>
-#include <linux/interrupt.h>
-#include <linux/io.h>
-#include <linux/mailbox_controller.h>
-#include <linux/module.h>
-#include <linux/of.h>
-#include <linux/platform_device.h>
-#include <linux/spinlock.h>
-#include <linux/types.h>
-
-#define APPLE_ASC_MBOX_CONTROL_FULL BIT(16)
-#define APPLE_ASC_MBOX_CONTROL_EMPTY BIT(17)
-
-#define APPLE_ASC_MBOX_A2I_CONTROL 0x110
-#define APPLE_ASC_MBOX_A2I_SEND0 0x800
-#define APPLE_ASC_MBOX_A2I_SEND1 0x808
-#define APPLE_ASC_MBOX_A2I_RECV0 0x810
-#define APPLE_ASC_MBOX_A2I_RECV1 0x818
-
-#define APPLE_ASC_MBOX_I2A_CONTROL 0x114
-#define APPLE_ASC_MBOX_I2A_SEND0 0x820
-#define APPLE_ASC_MBOX_I2A_SEND1 0x828
-#define APPLE_ASC_MBOX_I2A_RECV0 0x830
-#define APPLE_ASC_MBOX_I2A_RECV1 0x838
-
-#define APPLE_M3_MBOX_CONTROL_FULL BIT(16)
-#define APPLE_M3_MBOX_CONTROL_EMPTY BIT(17)
-
-#define APPLE_M3_MBOX_A2I_CONTROL 0x50
-#define APPLE_M3_MBOX_A2I_SEND0 0x60
-#define APPLE_M3_MBOX_A2I_SEND1 0x68
-#define APPLE_M3_MBOX_A2I_RECV0 0x70
-#define APPLE_M3_MBOX_A2I_RECV1 0x78
-
-#define APPLE_M3_MBOX_I2A_CONTROL 0x80
-#define APPLE_M3_MBOX_I2A_SEND0 0x90
-#define APPLE_M3_MBOX_I2A_SEND1 0x98
-#define APPLE_M3_MBOX_I2A_RECV0 0xa0
-#define APPLE_M3_MBOX_I2A_RECV1 0xa8
-
-#define APPLE_M3_MBOX_IRQ_ENABLE 0x48
-#define APPLE_M3_MBOX_IRQ_ACK 0x4c
-#define APPLE_M3_MBOX_IRQ_A2I_EMPTY BIT(0)
-#define APPLE_M3_MBOX_IRQ_A2I_NOT_EMPTY BIT(1)
-#define APPLE_M3_MBOX_IRQ_I2A_EMPTY BIT(2)
-#define APPLE_M3_MBOX_IRQ_I2A_NOT_EMPTY BIT(3)
-
-#define APPLE_MBOX_MSG1_OUTCNT GENMASK(56, 52)
-#define APPLE_MBOX_MSG1_INCNT GENMASK(51, 48)
-#define APPLE_MBOX_MSG1_OUTPTR GENMASK(47, 44)
-#define APPLE_MBOX_MSG1_INPTR GENMASK(43, 40)
-#define APPLE_MBOX_MSG1_MSG GENMASK(31, 0)
-
-struct apple_mbox_hw {
- unsigned int control_full;
- unsigned int control_empty;
-
- unsigned int a2i_control;
- unsigned int a2i_send0;
- unsigned int a2i_send1;
-
- unsigned int i2a_control;
- unsigned int i2a_recv0;
- unsigned int i2a_recv1;
-
- bool has_irq_controls;
- unsigned int irq_enable;
- unsigned int irq_ack;
- unsigned int irq_bit_recv_not_empty;
- unsigned int irq_bit_send_empty;
-};
-
-struct apple_mbox {
- void __iomem *regs;
- const struct apple_mbox_hw *hw;
-
- int irq_recv_not_empty;
- int irq_send_empty;
-
- struct mbox_chan chan;
-
- struct device *dev;
- struct mbox_controller controller;
- spinlock_t rx_lock;
-};
-
-static const struct of_device_id apple_mbox_of_match[];
-
-static bool apple_mbox_hw_can_send(struct apple_mbox *apple_mbox)
-{
- u32 mbox_ctrl =
- readl_relaxed(apple_mbox->regs + apple_mbox->hw->a2i_control);
-
- return !(mbox_ctrl & apple_mbox->hw->control_full);
-}
-
-static bool apple_mbox_hw_send_empty(struct apple_mbox *apple_mbox)
-{
- u32 mbox_ctrl =
- readl_relaxed(apple_mbox->regs + apple_mbox->hw->a2i_control);
-
- return mbox_ctrl & apple_mbox->hw->control_empty;
-}
-
-static int apple_mbox_hw_send(struct apple_mbox *apple_mbox,
- struct apple_mbox_msg *msg)
-{
- if (!apple_mbox_hw_can_send(apple_mbox))
- return -EBUSY;
-
- dev_dbg(apple_mbox->dev, "> TX %016llx %08x\n", msg->msg0, msg->msg1);
-
- writeq_relaxed(msg->msg0, apple_mbox->regs + apple_mbox->hw->a2i_send0);
- writeq_relaxed(FIELD_PREP(APPLE_MBOX_MSG1_MSG, msg->msg1),
- apple_mbox->regs + apple_mbox->hw->a2i_send1);
-
- return 0;
-}
-
-static bool apple_mbox_hw_can_recv(struct apple_mbox *apple_mbox)
-{
- u32 mbox_ctrl =
- readl_relaxed(apple_mbox->regs + apple_mbox->hw->i2a_control);
-
- return !(mbox_ctrl & apple_mbox->hw->control_empty);
-}
-
-static int apple_mbox_hw_recv(struct apple_mbox *apple_mbox,
- struct apple_mbox_msg *msg)
-{
- if (!apple_mbox_hw_can_recv(apple_mbox))
- return -ENOMSG;
-
- msg->msg0 = readq_relaxed(apple_mbox->regs + apple_mbox->hw->i2a_recv0);
- msg->msg1 = FIELD_GET(
- APPLE_MBOX_MSG1_MSG,
- readq_relaxed(apple_mbox->regs + apple_mbox->hw->i2a_recv1));
-
- dev_dbg(apple_mbox->dev, "< RX %016llx %08x\n", msg->msg0, msg->msg1);
-
- return 0;
-}
-
-static int apple_mbox_chan_send_data(struct mbox_chan *chan, void *data)
-{
- struct apple_mbox *apple_mbox = chan->con_priv;
- struct apple_mbox_msg *msg = data;
- int ret;
-
- ret = apple_mbox_hw_send(apple_mbox, msg);
- if (ret)
- return ret;
-
- /*
- * The interrupt is level triggered and will keep firing as long as the
- * FIFO is empty. It will also keep firing if the FIFO was empty
- * at any point in the past until it has been acknowledged at the
- * mailbox level. By acknowledging it here we can ensure that we will
- * only get the interrupt once the FIFO has been cleared again.
- * If the FIFO is already empty before the ack it will fire again
- * immediately after the ack.
- */
- if (apple_mbox->hw->has_irq_controls) {
- writel_relaxed(apple_mbox->hw->irq_bit_send_empty,
- apple_mbox->regs + apple_mbox->hw->irq_ack);
- }
- enable_irq(apple_mbox->irq_send_empty);
-
- return 0;
-}
-
-static irqreturn_t apple_mbox_send_empty_irq(int irq, void *data)
-{
- struct apple_mbox *apple_mbox = data;
-
- /*
- * We don't need to acknowledge the interrupt at the mailbox level
- * here even if supported by the hardware. It will keep firing but that
- * doesn't matter since it's disabled at the main interrupt controller.
- * apple_mbox_chan_send_data will acknowledge it before enabling
- * it at the main controller again.
- */
- disable_irq_nosync(apple_mbox->irq_send_empty);
- mbox_chan_txdone(&apple_mbox->chan, 0);
- return IRQ_HANDLED;
-}
-
-static int apple_mbox_poll(struct apple_mbox *apple_mbox)
-{
- struct apple_mbox_msg msg;
- int ret = 0;
-
- while (apple_mbox_hw_recv(apple_mbox, &msg) == 0) {
- mbox_chan_received_data(&apple_mbox->chan, (void *)&msg);
- ret++;
- }
-
- /*
- * The interrupt will keep firing even if there are no more messages
- * unless we also acknowledge it at the mailbox level here.
- * There's no race if a message comes in between the check in the while
- * loop above and the ack below: If a new messages arrives inbetween
- * those two the interrupt will just fire again immediately after the
- * ack since it's level triggered.
- */
- if (apple_mbox->hw->has_irq_controls) {
- writel_relaxed(apple_mbox->hw->irq_bit_recv_not_empty,
- apple_mbox->regs + apple_mbox->hw->irq_ack);
- }
-
- return ret;
-}
-
-static irqreturn_t apple_mbox_recv_irq(int irq, void *data)
-{
- struct apple_mbox *apple_mbox = data;
-
- spin_lock(&apple_mbox->rx_lock);
- apple_mbox_poll(apple_mbox);
- spin_unlock(&apple_mbox->rx_lock);
-
- return IRQ_HANDLED;
-}
-
-static bool apple_mbox_chan_peek_data(struct mbox_chan *chan)
-{
- struct apple_mbox *apple_mbox = chan->con_priv;
- unsigned long flags;
- int ret;
-
- spin_lock_irqsave(&apple_mbox->rx_lock, flags);
- ret = apple_mbox_poll(apple_mbox);
- spin_unlock_irqrestore(&apple_mbox->rx_lock, flags);
-
- return ret > 0;
-}
-
-static int apple_mbox_chan_flush(struct mbox_chan *chan, unsigned long timeout)
-{
- struct apple_mbox *apple_mbox = chan->con_priv;
- unsigned long deadline = jiffies + msecs_to_jiffies(timeout);
-
- while (time_before(jiffies, deadline)) {
- if (apple_mbox_hw_send_empty(apple_mbox)) {
- mbox_chan_txdone(&apple_mbox->chan, 0);
- return 0;
- }
-
- udelay(1);
- }
-
- return -ETIME;
-}
-
-static int apple_mbox_chan_startup(struct mbox_chan *chan)
-{
- struct apple_mbox *apple_mbox = chan->con_priv;
-
- /*
- * Only some variants of this mailbox HW provide interrupt control
- * at the mailbox level. We therefore need to handle enabling/disabling
- * interrupts at the main interrupt controller anyway for hardware that
- * doesn't. Just always keep the interrupts we care about enabled at
- * the mailbox level so that both hardware revisions behave almost
- * the same.
- */
- if (apple_mbox->hw->has_irq_controls) {
- writel_relaxed(apple_mbox->hw->irq_bit_recv_not_empty |
- apple_mbox->hw->irq_bit_send_empty,
- apple_mbox->regs + apple_mbox->hw->irq_enable);
- }
-
- enable_irq(apple_mbox->irq_recv_not_empty);
- return 0;
-}
-
-static void apple_mbox_chan_shutdown(struct mbox_chan *chan)
-{
- struct apple_mbox *apple_mbox = chan->con_priv;
-
- disable_irq(apple_mbox->irq_recv_not_empty);
-}
-
-static const struct mbox_chan_ops apple_mbox_ops = {
- .send_data = apple_mbox_chan_send_data,
- .peek_data = apple_mbox_chan_peek_data,
- .flush = apple_mbox_chan_flush,
- .startup = apple_mbox_chan_startup,
- .shutdown = apple_mbox_chan_shutdown,
-};
-
-static struct mbox_chan *apple_mbox_of_xlate(struct mbox_controller *mbox,
- const struct of_phandle_args *args)
-{
- if (args->args_count != 0)
- return ERR_PTR(-EINVAL);
-
- return &mbox->chans[0];
-}
-
-static int apple_mbox_probe(struct platform_device *pdev)
-{
- int ret;
- const struct of_device_id *match;
- char *irqname;
- struct apple_mbox *mbox;
- struct device *dev = &pdev->dev;
-
- match = of_match_node(apple_mbox_of_match, pdev->dev.of_node);
- if (!match)
- return -EINVAL;
- if (!match->data)
- return -EINVAL;
-
- mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL);
- if (!mbox)
- return -ENOMEM;
- platform_set_drvdata(pdev, mbox);
-
- mbox->dev = dev;
- mbox->regs = devm_platform_ioremap_resource(pdev, 0);
- if (IS_ERR(mbox->regs))
- return PTR_ERR(mbox->regs);
-
- mbox->hw = match->data;
- mbox->irq_recv_not_empty =
- platform_get_irq_byname(pdev, "recv-not-empty");
- if (mbox->irq_recv_not_empty < 0)
- return -ENODEV;
-
- mbox->irq_send_empty = platform_get_irq_byname(pdev, "send-empty");
- if (mbox->irq_send_empty < 0)
- return -ENODEV;
-
- mbox->controller.dev = mbox->dev;
- mbox->controller.num_chans = 1;
- mbox->controller.chans = &mbox->chan;
- mbox->controller.ops = &apple_mbox_ops;
- mbox->controller.txdone_irq = true;
- mbox->controller.of_xlate = apple_mbox_of_xlate;
- mbox->chan.con_priv = mbox;
- spin_lock_init(&mbox->rx_lock);
-
- irqname = devm_kasprintf(dev, GFP_KERNEL, "%s-recv", dev_name(dev));
- if (!irqname)
- return -ENOMEM;
-
- ret = devm_request_threaded_irq(dev, mbox->irq_recv_not_empty, NULL,
- apple_mbox_recv_irq,
- IRQF_NO_AUTOEN | IRQF_ONESHOT, irqname,
- mbox);
- if (ret)
- return ret;
-
- irqname = devm_kasprintf(dev, GFP_KERNEL, "%s-send", dev_name(dev));
- if (!irqname)
- return -ENOMEM;
-
- ret = devm_request_irq(dev, mbox->irq_send_empty,
- apple_mbox_send_empty_irq, IRQF_NO_AUTOEN,
- irqname, mbox);
- if (ret)
- return ret;
-
- return devm_mbox_controller_register(dev, &mbox->controller);
-}
-
-static const struct apple_mbox_hw apple_mbox_asc_hw = {
- .control_full = APPLE_ASC_MBOX_CONTROL_FULL,
- .control_empty = APPLE_ASC_MBOX_CONTROL_EMPTY,
-
- .a2i_control = APPLE_ASC_MBOX_A2I_CONTROL,
- .a2i_send0 = APPLE_ASC_MBOX_A2I_SEND0,
- .a2i_send1 = APPLE_ASC_MBOX_A2I_SEND1,
-
- .i2a_control = APPLE_ASC_MBOX_I2A_CONTROL,
- .i2a_recv0 = APPLE_ASC_MBOX_I2A_RECV0,
- .i2a_recv1 = APPLE_ASC_MBOX_I2A_RECV1,
-
- .has_irq_controls = false,
-};
-
-static const struct apple_mbox_hw apple_mbox_m3_hw = {
- .control_full = APPLE_M3_MBOX_CONTROL_FULL,
- .control_empty = APPLE_M3_MBOX_CONTROL_EMPTY,
-
- .a2i_control = APPLE_M3_MBOX_A2I_CONTROL,
- .a2i_send0 = APPLE_M3_MBOX_A2I_SEND0,
- .a2i_send1 = APPLE_M3_MBOX_A2I_SEND1,
-
- .i2a_control = APPLE_M3_MBOX_I2A_CONTROL,
- .i2a_recv0 = APPLE_M3_MBOX_I2A_RECV0,
- .i2a_recv1 = APPLE_M3_MBOX_I2A_RECV1,
-
- .has_irq_controls = true,
- .irq_enable = APPLE_M3_MBOX_IRQ_ENABLE,
- .irq_ack = APPLE_M3_MBOX_IRQ_ACK,
- .irq_bit_recv_not_empty = APPLE_M3_MBOX_IRQ_I2A_NOT_EMPTY,
- .irq_bit_send_empty = APPLE_M3_MBOX_IRQ_A2I_EMPTY,
-};
-
-static const struct of_device_id apple_mbox_of_match[] = {
- { .compatible = "apple,asc-mailbox-v4", .data = &apple_mbox_asc_hw },
- { .compatible = "apple,m3-mailbox-v2", .data = &apple_mbox_m3_hw },
- {}
-};
-MODULE_DEVICE_TABLE(of, apple_mbox_of_match);
-
-static struct platform_driver apple_mbox_driver = {
- .driver = {
- .name = "apple-mailbox",
- .of_match_table = apple_mbox_of_match,
- },
- .probe = apple_mbox_probe,
-};
-module_platform_driver(apple_mbox_driver);
-
-MODULE_LICENSE("Dual MIT/GPL");
-MODULE_AUTHOR("Sven Peter <sven@svenpeter.dev>");
-MODULE_DESCRIPTION("Apple Mailbox driver");
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 90ce58f..abd6e60 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -52,6 +52,21 @@
linear regulators, along with a complete ActivePath battery
charger.
+config MFD_APPLE_SPMI_PMU
+ tristate "Apple SPMI PMUs"
+ depends on SPMI
+ depends on ARCH_APPLE || COMPILE_TEST
+ default ARCH_APPLE
+ select MFD_SIMPLE_MFD_SPMI
+ help
+ Say yes here to enable support for Apple PMUs attached via the
+ SPMI bus. These can be found on Apple devices such as Apple
+ Silicon Macs.
+
+ This driver itself only attaches to the core device, and relies
+ on subsystem drivers for individual device functions. You must
+ enable those for it to be useful.
+
config MFD_SUN4I_GPADC
tristate "Allwinner sunxi platforms' GPADC MFD driver"
select MFD_CORE
@@ -1312,6 +1327,19 @@
sub-devices represented by child nodes in Device Tree will be
subsequently registered.
+config MFD_SIMPLE_MFD_SPMI
+ tristate
+ depends on SPMI
+ select MFD_CORE
+ select REGMAP_SPMI
+ help
+ This driver creates a single register map with the intention for it
+ to be shared by all sub-devices.
+
+ Once the register map has been successfully initialised, any
+ sub-devices represented by child nodes in Device Tree will be
+ subsequently registered.
+
config MFD_SL28CPLD
tristate "Kontron sl28cpld Board Management Controller"
depends on I2C
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index c66f07e..23ec49e 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -271,6 +271,7 @@
obj-$(CONFIG_SGI_MFD_IOC3) += ioc3.o
obj-$(CONFIG_MFD_SIMPLE_MFD_I2C) += simple-mfd-i2c.o
+obj-$(CONFIG_MFD_SIMPLE_MFD_SPMI) += simple-mfd-spmi.o
obj-$(CONFIG_MFD_SMPRO) += smpro-core.o
obj-$(CONFIG_MFD_INTEL_M10_BMC_CORE) += intel-m10-bmc-core.o
diff --git a/drivers/mfd/simple-mfd-spmi.c b/drivers/mfd/simple-mfd-spmi.c
new file mode 100644
index 0000000..99f2575
--- /dev/null
+++ b/drivers/mfd/simple-mfd-spmi.c
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Simple MFD - SPMI
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/spmi.h>
+#include <linux/of_platform.h>
+
+static const struct regmap_config spmi_regmap_config = {
+ .reg_bits = 16,
+ .val_bits = 8,
+ .max_register = 0xffff,
+};
+
+static int simple_spmi_probe(struct spmi_device *sdev)
+{
+ struct regmap *regmap;
+
+ regmap = devm_regmap_init_spmi_ext(sdev, &spmi_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ return devm_of_platform_populate(&sdev->dev);
+}
+
+static const struct of_device_id simple_spmi_id_table[] = {
+ { .compatible = "apple,spmi-pmu" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, simple_spmi_id_table);
+
+static struct spmi_driver pmic_spmi_driver = {
+ .probe = simple_spmi_probe,
+ .driver = {
+ .name = "simple-mfd-spmi",
+ .owner = THIS_MODULE,
+ .of_match_table = simple_spmi_id_table,
+ },
+};
+module_spmi_driver(pmic_spmi_driver);
+
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_DESCRIPTION("Simple MFD - SPMI driver");
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
diff --git a/drivers/mmc/host/sdhci-pci-core.c b/drivers/mmc/host/sdhci-pci-core.c
index 025b31a..0134c99 100644
--- a/drivers/mmc/host/sdhci-pci-core.c
+++ b/drivers/mmc/host/sdhci-pci-core.c
@@ -26,6 +26,7 @@
#include <linux/debugfs.h>
#include <linux/acpi.h>
#include <linux/dmi.h>
+#include <linux/of.h>
#include <linux/mmc/host.h>
#include <linux/mmc/mmc.h>
@@ -2042,6 +2043,7 @@ static struct sdhci_pci_slot *sdhci_pci_probe_slot(
struct sdhci_host *host;
int ret, bar = first_bar + slotno;
size_t priv_size = chip->fixes ? chip->fixes->priv_size : 0;
+ u32 cd_debounce_delay_ms;
if (!(pci_resource_flags(pdev, bar) & IORESOURCE_MEM)) {
dev_err(&pdev->dev, "BAR %d is not iomem. Aborting.\n", bar);
@@ -2108,6 +2110,10 @@ static struct sdhci_pci_slot *sdhci_pci_probe_slot(
if (host->mmc->caps & MMC_CAP_CD_WAKE)
device_init_wakeup(&pdev->dev, true);
+ if (device_property_read_u32(&pdev->dev, "cd-debounce-delay-ms",
+ &cd_debounce_delay_ms))
+ cd_debounce_delay_ms = 200;
+
if (slot->cd_idx >= 0) {
ret = mmc_gpiod_request_cd(host->mmc, "cd", slot->cd_idx,
slot->cd_override_level, 0);
@@ -2115,7 +2121,7 @@ static struct sdhci_pci_slot *sdhci_pci_probe_slot(
ret = mmc_gpiod_request_cd(host->mmc, NULL,
slot->cd_idx,
slot->cd_override_level,
- 0);
+ cd_debounce_delay_ms * 1000);
if (ret == -EPROBE_DEFER)
goto remove;
@@ -2123,6 +2129,16 @@ static struct sdhci_pci_slot *sdhci_pci_probe_slot(
dev_warn(&pdev->dev, "failed to setup card detect gpio\n");
slot->cd_idx = -1;
}
+ } else if (is_of_node(pdev->dev.fwnode)) {
+ /* Allow all OF systems to use a CD GPIO if provided */
+
+ ret = mmc_gpiod_request_cd(host->mmc, "cd", 0,
+ slot->cd_override_level,
+ cd_debounce_delay_ms * 1000);
+ if (ret == -EPROBE_DEFER)
+ goto remove;
+ else if (ret == 0)
+ slot->cd_idx = 0;
}
if (chip->fixes && chip->fixes->add_host)
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h
index fe31051..5efd7f6 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h
@@ -107,6 +107,7 @@ struct brcmf_bus_ops {
void (*debugfs_create)(struct device *dev);
int (*reset)(struct device *dev);
void (*remove)(struct device *dev);
+ void (*d2h_mb_rx)(struct device *dev, u32 data);
};
@@ -286,6 +287,15 @@ static inline void brcmf_bus_remove(struct brcmf_bus *bus)
bus->ops->remove(bus->dev);
}
+static inline
+void brcmf_bus_d2h_mb_rx(struct brcmf_bus *bus, u32 data)
+{
+ if (!bus->ops->d2h_mb_rx)
+ return;
+
+ return bus->ops->d2h_mb_rx(bus->dev, data);
+}
+
/*
* interface functions from common layer
*/
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
index 6674623..b15a662 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
@@ -1687,52 +1687,44 @@ static u16 brcmf_map_fw_linkdown_reason(const struct brcmf_event_msg *e)
return reason;
}
-static int brcmf_set_pmk(struct brcmf_if *ifp, const u8 *pmk_data, u16 pmk_len)
+static int brcmf_set_wsec(struct brcmf_if *ifp, const u8 *key, u16 key_len, u16 flags)
{
struct brcmf_pub *drvr = ifp->drvr;
struct brcmf_wsec_pmk_le pmk;
int err;
+ if (key_len > sizeof(pmk.key)) {
+ bphy_err(drvr, "key must be less than %zu bytes\n",
+ sizeof(pmk.key));
+ return -EINVAL;
+ }
+
memset(&pmk, 0, sizeof(pmk));
- /* pass pmk directly */
- pmk.key_len = cpu_to_le16(pmk_len);
- pmk.flags = cpu_to_le16(0);
- memcpy(pmk.key, pmk_data, pmk_len);
+ /* pass key material directly */
+ pmk.key_len = cpu_to_le16(key_len);
+ pmk.flags = cpu_to_le16(flags);
+ memcpy(pmk.key, key, key_len);
- /* store psk in firmware */
+ /* store key material in firmware */
err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SET_WSEC_PMK,
&pmk, sizeof(pmk));
if (err < 0)
bphy_err(drvr, "failed to change PSK in firmware (len=%u)\n",
- pmk_len);
+ key_len);
return err;
}
+static int brcmf_set_pmk(struct brcmf_if *ifp, const u8 *pmk_data, u16 pmk_len)
+{
+ return brcmf_set_wsec(ifp, pmk_data, pmk_len, 0);
+}
+
static int brcmf_set_sae_password(struct brcmf_if *ifp, const u8 *pwd_data,
u16 pwd_len)
{
- struct brcmf_pub *drvr = ifp->drvr;
- struct brcmf_wsec_sae_pwd_le sae_pwd;
- int err;
-
- if (pwd_len > BRCMF_WSEC_MAX_SAE_PASSWORD_LEN) {
- bphy_err(drvr, "sae_password must be less than %d\n",
- BRCMF_WSEC_MAX_SAE_PASSWORD_LEN);
- return -EINVAL;
- }
-
- sae_pwd.key_len = cpu_to_le16(pwd_len);
- memcpy(sae_pwd.key, pwd_data, pwd_len);
-
- err = brcmf_fil_iovar_data_set(ifp, "sae_password", &sae_pwd,
- sizeof(sae_pwd));
- if (err < 0)
- bphy_err(drvr, "failed to set SAE password in firmware (len=%u)\n",
- pwd_len);
-
- return err;
+ return brcmf_set_wsec(ifp, pwd_data, pwd_len, BRCMF_WSEC_PASSPHRASE);
}
static void brcmf_link_down(struct brcmf_cfg80211_vif *vif, u16 reason,
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
index 9d248ba..e74a23e 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
@@ -584,7 +584,7 @@ struct brcmf_wsec_key_le {
struct brcmf_wsec_pmk_le {
__le16 key_len;
__le16 flags;
- u8 key[2 * BRCMF_WSEC_MAX_PSK_LEN + 1];
+ u8 key[BRCMF_WSEC_MAX_SAE_PASSWORD_LEN];
};
/**
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c
index 45fbcbd..9320685 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c
@@ -47,6 +47,32 @@
#define MSGBUF_TYPE_RX_CMPLT 0x12
#define MSGBUF_TYPE_LPBK_DMAXFER 0x13
#define MSGBUF_TYPE_LPBK_DMAXFER_CMPLT 0x14
+#define MSGBUF_TYPE_FLOW_RING_RESUME 0x15
+#define MSGBUF_TYPE_FLOW_RING_RESUME_CMPLT 0x16
+#define MSGBUF_TYPE_FLOW_RING_SUSPEND 0x17
+#define MSGBUF_TYPE_FLOW_RING_SUSPEND_CMPLT 0x18
+#define MSGBUF_TYPE_INFO_BUF_POST 0x19
+#define MSGBUF_TYPE_INFO_BUF_CMPLT 0x1A
+#define MSGBUF_TYPE_H2D_RING_CREATE 0x1B
+#define MSGBUF_TYPE_D2H_RING_CREATE 0x1C
+#define MSGBUF_TYPE_H2D_RING_CREATE_CMPLT 0x1D
+#define MSGBUF_TYPE_D2H_RING_CREATE_CMPLT 0x1E
+#define MSGBUF_TYPE_H2D_RING_CONFIG 0x1F
+#define MSGBUF_TYPE_D2H_RING_CONFIG 0x20
+#define MSGBUF_TYPE_H2D_RING_CONFIG_CMPLT 0x21
+#define MSGBUF_TYPE_D2H_RING_CONFIG_CMPLT 0x22
+#define MSGBUF_TYPE_H2D_MAILBOX_DATA 0x23
+#define MSGBUF_TYPE_D2H_MAILBOX_DATA 0x24
+#define MSGBUF_TYPE_TIMSTAMP_BUFPOST 0x25
+#define MSGBUF_TYPE_HOSTTIMSTAMP 0x26
+#define MSGBUF_TYPE_HOSTTIMSTAMP_CMPLT 0x27
+#define MSGBUF_TYPE_FIRMWARE_TIMESTAMP 0x28
+#define MSGBUF_TYPE_SNAPSHOT_UPLOAD 0x29
+#define MSGBUF_TYPE_SNAPSHOT_CMPLT 0x2A
+#define MSGBUF_TYPE_H2D_RING_DELETE 0x2B
+#define MSGBUF_TYPE_D2H_RING_DELETE 0x2C
+#define MSGBUF_TYPE_H2D_RING_DELETE_CMPLT 0x2D
+#define MSGBUF_TYPE_D2H_RING_DELETE_CMPLT 0x2E
#define NR_TX_PKTIDS 2048
#define NR_RX_PKTIDS 1024
@@ -218,6 +244,19 @@ struct msgbuf_flowring_flush_resp {
__le32 rsvd0[3];
};
+struct msgbuf_h2d_mailbox_data {
+ struct msgbuf_common_hdr msg;
+ __le32 data;
+ __le32 rsvd0[7];
+};
+
+struct msgbuf_d2h_mailbox_data {
+ struct msgbuf_common_hdr msg;
+ struct msgbuf_completion_hdr compl_hdr;
+ __le32 data;
+ __le32 rsvd0[2];
+};
+
struct brcmf_msgbuf_work_item {
struct list_head queue;
u32 flowid;
@@ -1285,6 +1324,16 @@ brcmf_msgbuf_process_flow_ring_delete_response(struct brcmf_msgbuf *msgbuf,
}
+static void brcmf_msgbuf_process_d2h_mailbox_data(struct brcmf_msgbuf *msgbuf,
+ void *buf)
+{
+ struct msgbuf_d2h_mailbox_data *d2h_mb_data = buf;
+ struct brcmf_pub *drvr = msgbuf->drvr;
+
+ brcmf_bus_d2h_mb_rx(drvr->bus_if, le32_to_cpu(d2h_mb_data->data));
+}
+
+
static void brcmf_msgbuf_process_msgtype(struct brcmf_msgbuf *msgbuf, void *buf)
{
struct brcmf_pub *drvr = msgbuf->drvr;
@@ -1327,6 +1376,10 @@ static void brcmf_msgbuf_process_msgtype(struct brcmf_msgbuf *msgbuf, void *buf)
brcmf_dbg(MSGBUF, "MSGBUF_TYPE_RX_CMPLT\n");
brcmf_msgbuf_process_rx_complete(msgbuf, buf);
break;
+ case MSGBUF_TYPE_D2H_MAILBOX_DATA:
+ brcmf_dbg(MSGBUF, "MSGBUF_TYPE_D2H_MAILBOX_DATA\n");
+ brcmf_msgbuf_process_d2h_mailbox_data(msgbuf, buf);
+ break;
default:
bphy_err(drvr, "Unsupported msgtype %d\n", msg->msgtype);
break;
@@ -1465,6 +1518,38 @@ void brcmf_msgbuf_delete_flowring(struct brcmf_pub *drvr, u16 flowid)
}
}
+
+int brcmf_msgbuf_h2d_mb_write(struct brcmf_pub *drvr, u32 data)
+{
+ struct brcmf_msgbuf *msgbuf = (struct brcmf_msgbuf *)drvr->proto->pd;
+ struct brcmf_commonring *commonring;
+ struct msgbuf_h2d_mailbox_data *request;
+ void *ret_ptr;
+ int err;
+
+ commonring = msgbuf->commonrings[BRCMF_H2D_MSGRING_CONTROL_SUBMIT];
+ brcmf_commonring_lock(commonring);
+ ret_ptr = brcmf_commonring_reserve_for_write(commonring);
+ if (!ret_ptr) {
+ bphy_err(drvr, "Failed to reserve space in commonring\n");
+ brcmf_commonring_unlock(commonring);
+ return -ENOMEM;
+ }
+
+ request = (struct msgbuf_h2d_mailbox_data *)ret_ptr;
+ request->msg.msgtype = MSGBUF_TYPE_H2D_MAILBOX_DATA;
+ request->msg.ifidx = -1;
+ request->msg.flags = 0;
+ request->msg.request_id = 0;
+ request->data = data;
+
+ err = brcmf_commonring_write_complete(commonring);
+ brcmf_commonring_unlock(commonring);
+
+ return err;
+}
+
+
#ifdef DEBUG
static int brcmf_msgbuf_stats_read(struct seq_file *seq, void *data)
{
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.h
index 6a849f4..89b6b7f 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.h
@@ -32,6 +32,7 @@ int brcmf_proto_msgbuf_rx_trigger(struct device *dev);
void brcmf_msgbuf_delete_flowring(struct brcmf_pub *drvr, u16 flowid);
int brcmf_proto_msgbuf_attach(struct brcmf_pub *drvr);
void brcmf_proto_msgbuf_detach(struct brcmf_pub *drvr);
+int brcmf_msgbuf_h2d_mb_write(struct brcmf_pub *drvr, u32 data);
#else
static inline int brcmf_proto_msgbuf_attach(struct brcmf_pub *drvr)
{
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c
index d4492d0..071b070 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c
@@ -1793,8 +1793,8 @@ bool brcmf_p2p_send_action_frame(struct brcmf_cfg80211_info *cfg,
/* do not configure anything. it will be */
/* sent with a default configuration */
} else {
- bphy_err(drvr, "Unknown Frame: category 0x%x, action 0x%x\n",
- category, action);
+ bphy_info_once(drvr, "Unknown Frame: category 0x%x, action 0x%x\n",
+ category, action);
return false;
}
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
index 8022068..2a3998a 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
@@ -215,11 +215,64 @@ static const struct brcmf_firmware_mapping brcmf_pcie_fwnames[] = {
#define BRCMF_PCIE_SHARED_VERSION_MASK 0x00FF
#define BRCMF_PCIE_SHARED_DMA_INDEX 0x10000
#define BRCMF_PCIE_SHARED_DMA_2B_IDX 0x100000
+#define BRCMF_PCIE_SHARED_USE_MAILBOX 0x2000000
+#define BRCMF_PCIE_SHARED_TIMESTAMP_DB0 0x8000000
#define BRCMF_PCIE_SHARED_HOSTRDY_DB1 0x10000000
+#define BRCMF_PCIE_SHARED_NO_OOB_DW 0x20000000
+#define BRCMF_PCIE_SHARED_INBAND_DS 0x40000000
+#define BRCMF_PCIE_SHARED_DAR 0x80000000
+
+#define BRCMF_PCIE_SHARED2_EXTENDED_TRAP_DATA 0x1
+#define BRCMF_PCIE_SHARED2_TXSTATUS_METADATA 0x2
+#define BRCMF_PCIE_SHARED2_BT_LOGGING 0x4
+#define BRCMF_PCIE_SHARED2_SNAPSHOT_UPLOAD 0x8
+#define BRCMF_PCIE_SHARED2_SUBMIT_COUNT_WAR 0x10
+#define BRCMF_PCIE_SHARED2_FAST_DELETE_RING 0x20
+#define BRCMF_PCIE_SHARED2_EVTBUF_MAX_MASK 0xC0
+#define BRCMF_PCIE_SHARED2_PKT_TX_STATUS 0x100
+#define BRCMF_PCIE_SHARED2_FW_SMALL_MEMDUMP 0x200
+#define BRCMF_PCIE_SHARED2_FW_HC_ON_TRAP 0x400
+#define BRCMF_PCIE_SHARED2_HSCB 0x800
+#define BRCMF_PCIE_SHARED2_EDL_RING 0x1000
+#define BRCMF_PCIE_SHARED2_DEBUG_BUF_DEST 0x2000
+#define BRCMF_PCIE_SHARED2_PCIE_ENUM_RESET_FLR 0x4000
+#define BRCMF_PCIE_SHARED2_PKT_TIMESTAMP 0x8000
+#define BRCMF_PCIE_SHARED2_HP2P 0x10000
+#define BRCMF_PCIE_SHARED2_HWA 0x20000
+#define BRCMF_PCIE_SHARED2_TRAP_ON_HOST_DB7 0x40000
+#define BRCMF_PCIE_SHARED2_DURATION_SCALE 0x100000
+#define BRCMF_PCIE_SHARED2_D2H_D11_TX_STATUS 0x40000000
+#define BRCMF_PCIE_SHARED2_H2D_D11_TX_STATUS 0x80000000
#define BRCMF_PCIE_FLAGS_HTOD_SPLIT 0x4000
#define BRCMF_PCIE_FLAGS_DTOH_SPLIT 0x8000
+#define BRCMF_HOSTCAP_PCIEAPI_VERSION_MASK 0x000000FF
+#define BRCMF_HOSTCAP_H2D_VALID_PHASE 0x00000100
+#define BRCMF_HOSTCAP_H2D_ENABLE_TRAP_ON_BADPHASE 0x00000200
+#define BRCMF_HOSTCAP_H2D_ENABLE_HOSTRDY 0x400
+#define BRCMF_HOSTCAP_DB0_TIMESTAMP 0x800
+#define BRCMF_HOSTCAP_DS_NO_OOB_DW 0x1000
+#define BRCMF_HOSTCAP_DS_INBAND_DW 0x2000
+#define BRCMF_HOSTCAP_H2D_IDMA 0x4000
+#define BRCMF_HOSTCAP_H2D_IFRM 0x8000
+#define BRCMF_HOSTCAP_H2D_DAR 0x10000
+#define BRCMF_HOSTCAP_EXTENDED_TRAP_DATA 0x20000
+#define BRCMF_HOSTCAP_TXSTATUS_METADATA 0x40000
+#define BRCMF_HOSTCAP_BT_LOGGING 0x80000
+#define BRCMF_HOSTCAP_SNAPSHOT_UPLOAD 0x100000
+#define BRCMF_HOSTCAP_FAST_DELETE_RING 0x200000
+#define BRCMF_HOSTCAP_PKT_TXSTATUS 0x400000
+#define BRCMF_HOSTCAP_UR_FW_NO_TRAP 0x800000
+#define BRCMF_HOSTCAP_HSCB 0x2000000
+#define BRCMF_HOSTCAP_EXT_TRAP_DBGBUF 0x4000000
+#define BRCMF_HOSTCAP_EDL_RING 0x10000000
+#define BRCMF_HOSTCAP_PKT_TIMESTAMP 0x20000000
+#define BRCMF_HOSTCAP_PKT_HP2P 0x40000000
+#define BRCMF_HOSTCAP_HWA 0x80000000
+#define BRCMF_HOSTCAP2_DURATION_SCALE_MASK 0x3F
+
+#define BRCMF_SHARED_FLAGS_OFFSET 0
#define BRCMF_SHARED_MAX_RXBUFPOST_OFFSET 34
#define BRCMF_SHARED_RING_BASE_OFFSET 52
#define BRCMF_SHARED_RX_DATAOFFSET_OFFSET 36
@@ -231,6 +284,11 @@ static const struct brcmf_firmware_mapping brcmf_pcie_fwnames[] = {
#define BRCMF_SHARED_DMA_SCRATCH_ADDR_OFFSET 56
#define BRCMF_SHARED_DMA_RINGUPD_LEN_OFFSET 64
#define BRCMF_SHARED_DMA_RINGUPD_ADDR_OFFSET 68
+#define BRCMF_SHARED_FLAGS2_OFFSET 80
+#define BRCMF_SHARED_HOST_CAP_OFFSET 84
+#define BRCMF_SHARED_FLAGS3_OFFSET 108
+#define BRCMF_SHARED_HOST_CAP2_OFFSET 112
+#define BRCMF_SHARED_HOST_CAP3_OFFSET 116
#define BRCMF_RING_H2D_RING_COUNT_OFFSET 0
#define BRCMF_RING_D2H_RING_COUNT_OFFSET 1
@@ -295,6 +353,8 @@ struct brcmf_pcie_console {
struct brcmf_pcie_shared_info {
u32 tcm_base_address;
u32 flags;
+ u32 flags2;
+ u32 flags3;
struct brcmf_pcie_ringbuf *commonrings[BRCMF_NROF_COMMON_MSGRINGS];
struct brcmf_pcie_ringbuf *flowrings;
u16 max_rxbufpost;
@@ -311,6 +371,7 @@ struct brcmf_pcie_shared_info {
void *ringupd;
dma_addr_t ringupd_dmahandle;
u8 version;
+ bool mb_via_ctl;
};
struct brcmf_pcie_core_info {
@@ -348,6 +409,8 @@ struct brcmf_pciedev_info {
wait_queue_head_t mbdata_resp_wait;
bool mbdata_completed;
bool irq_allocated;
+ bool irq_ready;
+ bool have_msi;
bool wowl_enabled;
u8 dma_idx_sz;
void *idxbuf;
@@ -433,8 +496,6 @@ struct brcmf_pcie_reginfo {
u32 intmask;
u32 mailboxint;
u32 mailboxmask;
- u32 h2d_mailbox_0;
- u32 h2d_mailbox_1;
u32 int_d2h_db;
u32 int_fn0;
};
@@ -443,8 +504,6 @@ static const struct brcmf_pcie_reginfo brcmf_reginfo_default = {
.intmask = BRCMF_PCIE_PCIE2REG_INTMASK,
.mailboxint = BRCMF_PCIE_PCIE2REG_MAILBOXINT,
.mailboxmask = BRCMF_PCIE_PCIE2REG_MAILBOXMASK,
- .h2d_mailbox_0 = BRCMF_PCIE_PCIE2REG_H2D_MAILBOX_0,
- .h2d_mailbox_1 = BRCMF_PCIE_PCIE2REG_H2D_MAILBOX_1,
.int_d2h_db = BRCMF_PCIE_MB_INT_D2H_DB,
.int_fn0 = BRCMF_PCIE_MB_INT_FN0,
};
@@ -453,8 +512,6 @@ static const struct brcmf_pcie_reginfo brcmf_reginfo_64 = {
.intmask = BRCMF_PCIE_64_PCIE2REG_INTMASK,
.mailboxint = BRCMF_PCIE_64_PCIE2REG_MAILBOXINT,
.mailboxmask = BRCMF_PCIE_64_PCIE2REG_MAILBOXMASK,
- .h2d_mailbox_0 = BRCMF_PCIE_64_PCIE2REG_H2D_MAILBOX_0,
- .h2d_mailbox_1 = BRCMF_PCIE_64_PCIE2REG_H2D_MAILBOX_1,
.int_d2h_db = BRCMF_PCIE_64_MB_INT_D2H_DB,
.int_fn0 = 0,
};
@@ -767,6 +824,19 @@ brcmf_pcie_send_mb_data(struct brcmf_pciedev_info *devinfo, u32 htod_mb_data)
u32 i;
shared = &devinfo->shared;
+
+ if (shared->mb_via_ctl) {
+ struct pci_dev *pdev = devinfo->pdev;
+ struct brcmf_bus *bus = dev_get_drvdata(&pdev->dev);
+ int ret;
+
+ ret = brcmf_msgbuf_h2d_mb_write(bus->drvr, htod_mb_data);
+ if (ret < 0)
+ brcmf_err(bus, "Failed to send H2D mailbox data (%d)\n",
+ ret);
+ return ret;
+ }
+
addr = shared->htod_mb_data_addr;
cur_htod_mb_data = brcmf_pcie_read_tcm32(devinfo, addr);
@@ -794,8 +864,29 @@ brcmf_pcie_send_mb_data(struct brcmf_pciedev_info *devinfo, u32 htod_mb_data)
return 0;
}
+static void brcmf_pcie_handle_mb_data(struct brcmf_pciedev_info *devinfo, u32 data)
+{
+ brcmf_dbg(PCIE, "D2H_MB_DATA: 0x%04x\n", data);
+ if (data & BRCMF_D2H_DEV_DS_ENTER_REQ) {
+ brcmf_dbg(PCIE, "D2H_MB_DATA: DEEP SLEEP REQ\n");
+ brcmf_pcie_send_mb_data(devinfo, BRCMF_H2D_HOST_DS_ACK);
+ brcmf_dbg(PCIE, "D2H_MB_DATA: sent DEEP SLEEP ACK\n");
+ }
+ if (data & BRCMF_D2H_DEV_DS_EXIT_NOTE)
+ brcmf_dbg(PCIE, "D2H_MB_DATA: DEEP SLEEP EXIT\n");
+ if (data & BRCMF_D2H_DEV_D3_ACK) {
+ brcmf_dbg(PCIE, "D2H_MB_DATA: D3 ACK\n");
+ devinfo->mbdata_completed = true;
+ wake_up(&devinfo->mbdata_resp_wait);
+ }
+ if (data & BRCMF_D2H_DEV_FWHALT) {
+ brcmf_dbg(PCIE, "D2H_MB_DATA: FW HALT\n");
+ brcmf_fw_crashed(&devinfo->pdev->dev);
+ }
+}
-static void brcmf_pcie_handle_mb_data(struct brcmf_pciedev_info *devinfo)
+
+static void brcmf_pcie_poll_mb_data(struct brcmf_pciedev_info *devinfo)
{
struct brcmf_pcie_shared_info *shared;
u32 addr;
@@ -810,23 +901,16 @@ static void brcmf_pcie_handle_mb_data(struct brcmf_pciedev_info *devinfo)
brcmf_pcie_write_tcm32(devinfo, addr, 0);
- brcmf_dbg(PCIE, "D2H_MB_DATA: 0x%04x\n", dtoh_mb_data);
- if (dtoh_mb_data & BRCMF_D2H_DEV_DS_ENTER_REQ) {
- brcmf_dbg(PCIE, "D2H_MB_DATA: DEEP SLEEP REQ\n");
- brcmf_pcie_send_mb_data(devinfo, BRCMF_H2D_HOST_DS_ACK);
- brcmf_dbg(PCIE, "D2H_MB_DATA: sent DEEP SLEEP ACK\n");
- }
- if (dtoh_mb_data & BRCMF_D2H_DEV_DS_EXIT_NOTE)
- brcmf_dbg(PCIE, "D2H_MB_DATA: DEEP SLEEP EXIT\n");
- if (dtoh_mb_data & BRCMF_D2H_DEV_D3_ACK) {
- brcmf_dbg(PCIE, "D2H_MB_DATA: D3 ACK\n");
- devinfo->mbdata_completed = true;
- wake_up(&devinfo->mbdata_resp_wait);
- }
- if (dtoh_mb_data & BRCMF_D2H_DEV_FWHALT) {
- brcmf_dbg(PCIE, "D2H_MB_DATA: FW HALT\n");
- brcmf_fw_crashed(&devinfo->pdev->dev);
- }
+ brcmf_pcie_handle_mb_data(devinfo, dtoh_mb_data);
+}
+
+
+static void brcmf_pcie_d2h_mb_rx(struct device *dev, u32 data)
+{
+ struct brcmf_bus *bus_if = dev_get_drvdata(dev);
+ struct brcmf_pciedev *buspub = bus_if->bus_priv.pcie;
+
+ brcmf_pcie_handle_mb_data(buspub->devinfo, data);
}
@@ -906,6 +990,8 @@ static void brcmf_pcie_bus_console_read(struct brcmf_pciedev_info *devinfo,
static void brcmf_pcie_intr_disable(struct brcmf_pciedev_info *devinfo)
{
brcmf_pcie_write_reg32(devinfo, devinfo->reginfo->mailboxmask, 0);
+
+ devinfo->irq_ready = false;
}
@@ -914,13 +1000,18 @@ static void brcmf_pcie_intr_enable(struct brcmf_pciedev_info *devinfo)
brcmf_pcie_write_reg32(devinfo, devinfo->reginfo->mailboxmask,
devinfo->reginfo->int_d2h_db |
devinfo->reginfo->int_fn0);
+
+ devinfo->irq_ready = true;
}
static void brcmf_pcie_hostready(struct brcmf_pciedev_info *devinfo)
{
- if (devinfo->shared.flags & BRCMF_PCIE_SHARED_HOSTRDY_DB1)
- brcmf_pcie_write_reg32(devinfo,
- devinfo->reginfo->h2d_mailbox_1, 1);
+ if (devinfo->shared.flags & BRCMF_PCIE_SHARED_HOSTRDY_DB1) {
+ if (devinfo->shared.flags & BRCMF_PCIE_SHARED_DAR)
+ brcmf_pcie_write_reg32(devinfo, BRCMF_PCIE_64_PCIE2REG_H2D_MAILBOX_1, 1);
+ else
+ brcmf_pcie_write_reg32(devinfo, BRCMF_PCIE_PCIE2REG_H2D_MAILBOX_1, 1);
+ }
}
static irqreturn_t brcmf_pcie_quick_check_isr(int irq, void *arg)
@@ -932,6 +1023,11 @@ static irqreturn_t brcmf_pcie_quick_check_isr(int irq, void *arg)
brcmf_dbg(PCIE, "Enter\n");
return IRQ_WAKE_THREAD;
}
+
+ /* mailboxint is cleared by the firmware in MSI mode */
+ if (devinfo->have_msi)
+ return IRQ_WAKE_THREAD;
+
return IRQ_NONE;
}
@@ -948,13 +1044,13 @@ static irqreturn_t brcmf_pcie_isr_thread(int irq, void *arg)
brcmf_pcie_write_reg32(devinfo, devinfo->reginfo->mailboxint,
status);
if (status & devinfo->reginfo->int_fn0)
- brcmf_pcie_handle_mb_data(devinfo);
- if (status & devinfo->reginfo->int_d2h_db) {
- if (devinfo->state == BRCMFMAC_PCIE_STATE_UP)
- brcmf_proto_msgbuf_rx_trigger(
- &devinfo->pdev->dev);
- }
+ brcmf_pcie_poll_mb_data(devinfo);
}
+ if (devinfo->have_msi || status & devinfo->reginfo->int_d2h_db) {
+ if (devinfo->state == BRCMFMAC_PCIE_STATE_UP && devinfo->irq_ready)
+ brcmf_proto_msgbuf_rx_trigger(&devinfo->pdev->dev);
+ }
+
brcmf_pcie_bus_console_read(devinfo, false);
if (devinfo->state == BRCMFMAC_PCIE_STATE_UP)
brcmf_pcie_intr_enable(devinfo);
@@ -972,7 +1068,10 @@ static int brcmf_pcie_request_irq(struct brcmf_pciedev_info *devinfo)
brcmf_dbg(PCIE, "Enter\n");
- pci_enable_msi(pdev);
+ devinfo->have_msi = pci_enable_msi(pdev) >= 0;
+ if (devinfo->have_msi)
+ brcmf_dbg(PCIE, "MSI enabled\n");
+
if (request_threaded_irq(pdev->irq, brcmf_pcie_quick_check_isr,
brcmf_pcie_isr_thread, IRQF_SHARED,
"brcmf_pcie_intr", devinfo)) {
@@ -1061,7 +1160,10 @@ static int brcmf_pcie_ring_mb_ring_bell(void *ctx)
brcmf_dbg(PCIE, "RING !\n");
/* Any arbitrary value will do, lets use 1 */
- brcmf_pcie_write_reg32(devinfo, devinfo->reginfo->h2d_mailbox_0, 1);
+ if (devinfo->shared.flags & BRCMF_PCIE_SHARED_DAR)
+ brcmf_pcie_write_reg32(devinfo, BRCMF_PCIE_64_PCIE2REG_H2D_MAILBOX_0, 1);
+ else
+ brcmf_pcie_write_reg32(devinfo, BRCMF_PCIE_PCIE2REG_H2D_MAILBOX_0, 1);
return 0;
}
@@ -1587,6 +1689,7 @@ static const struct brcmf_bus_ops brcmf_pcie_bus_ops = {
.get_blob = brcmf_pcie_get_blob,
.reset = brcmf_pcie_reset,
.debugfs_create = brcmf_pcie_debugfs_create,
+ .d2h_mb_rx = brcmf_pcie_d2h_mb_rx,
};
@@ -1618,12 +1721,16 @@ brcmf_pcie_init_share_ram_info(struct brcmf_pciedev_info *devinfo,
{
struct brcmf_bus *bus = dev_get_drvdata(&devinfo->pdev->dev);
struct brcmf_pcie_shared_info *shared;
+ u32 host_cap;
+ u32 host_cap2;
u32 addr;
shared = &devinfo->shared;
shared->tcm_base_address = sharedram_addr;
- shared->flags = brcmf_pcie_read_tcm32(devinfo, sharedram_addr);
+ shared->flags = brcmf_pcie_read_tcm32(devinfo, sharedram_addr +
+ BRCMF_SHARED_FLAGS_OFFSET);
+
shared->version = (u8)(shared->flags & BRCMF_PCIE_SHARED_VERSION_MASK);
brcmf_dbg(PCIE, "PCIe protocol version %d\n", shared->version);
if ((shared->version > BRCMF_PCIE_MAX_SHARED_VERSION) ||
@@ -1664,6 +1771,37 @@ brcmf_pcie_init_share_ram_info(struct brcmf_pciedev_info *devinfo,
brcmf_pcie_bus_console_init(devinfo);
brcmf_pcie_bus_console_read(devinfo, false);
+ /* Features added in revision 6 follow */
+ if (shared->version < 6)
+ return 0;
+
+ shared->flags2 = brcmf_pcie_read_tcm32(devinfo, sharedram_addr +
+ BRCMF_SHARED_FLAGS2_OFFSET);
+ shared->flags3 = brcmf_pcie_read_tcm32(devinfo, sharedram_addr +
+ BRCMF_SHARED_FLAGS3_OFFSET);
+
+ /* Check which mailbox mechanism to use */
+ if (!(shared->flags & BRCMF_PCIE_SHARED_USE_MAILBOX))
+ shared->mb_via_ctl = true;
+
+ /* Update host support flags */
+ host_cap = shared->version;
+ host_cap2 = 0;
+
+ if (shared->flags & BRCMF_PCIE_SHARED_HOSTRDY_DB1)
+ host_cap |= BRCMF_HOSTCAP_H2D_ENABLE_HOSTRDY;
+
+ if (shared->flags & BRCMF_PCIE_SHARED_DAR)
+ host_cap |= BRCMF_HOSTCAP_H2D_DAR;
+
+ /* Disable DS: this is not currently properly supported */
+ host_cap |= BRCMF_HOSTCAP_DS_NO_OOB_DW;
+
+ brcmf_pcie_write_tcm32(devinfo, sharedram_addr +
+ BRCMF_SHARED_HOST_CAP_OFFSET, host_cap);
+ brcmf_pcie_write_tcm32(devinfo, sharedram_addr +
+ BRCMF_SHARED_HOST_CAP2_OFFSET, host_cap2);
+
return 0;
}
@@ -2618,10 +2756,11 @@ static int brcmf_pcie_pm_leave_D3(struct device *dev)
/* Check if device is still up and running, if so we are ready */
if (brcmf_pcie_read_reg32(devinfo, devinfo->reginfo->intmask) != 0) {
brcmf_dbg(PCIE, "Try to wakeup device....\n");
+ /* Set the device up, so we can write the MB data message in ring mode */
+ devinfo->state = BRCMFMAC_PCIE_STATE_UP;
if (brcmf_pcie_send_mb_data(devinfo, BRCMF_H2D_HOST_D0_INFORM))
goto cleanup;
brcmf_dbg(PCIE, "Hot resume, continue....\n");
- devinfo->state = BRCMFMAC_PCIE_STATE_UP;
brcmf_pcie_select_core(devinfo, BCMA_CORE_PCIE2);
brcmf_bus_change_state(bus, BRCMF_BUS_UP);
brcmf_pcie_intr_enable(devinfo);
@@ -2631,6 +2770,7 @@ static int brcmf_pcie_pm_leave_D3(struct device *dev)
}
cleanup:
+ devinfo->state = BRCMFMAC_PCIE_STATE_DOWN;
brcmf_chip_detach(devinfo->ci);
devinfo->ci = NULL;
pdev = devinfo->pdev;
diff --git a/drivers/nvme/host/apple.c b/drivers/nvme/host/apple.c
index 596bb11..54e521e 100644
--- a/drivers/nvme/host/apple.c
+++ b/drivers/nvme/host/apple.c
@@ -195,8 +195,20 @@ struct apple_nvme {
int irq;
spinlock_t lock;
+
+ /*
+ * Delayed cache flush handling state
+ */
+ struct nvme_ns *flush_ns;
+ unsigned long flush_interval;
+ unsigned long last_flush;
+ struct delayed_work flush_dwork;
};
+unsigned int flush_interval = 1000;
+module_param(flush_interval, uint, 0644);
+MODULE_PARM_DESC(flush_interval, "Grace period in msecs between flushes");
+
static_assert(sizeof(struct nvme_command) == 64);
static_assert(sizeof(struct apple_nvmmu_tcb) == 128);
@@ -729,6 +741,26 @@ static int apple_nvme_remove_sq(struct apple_nvme *anv)
return nvme_submit_sync_cmd(anv->ctrl.admin_q, &c, NULL, 0);
}
+static bool apple_nvme_delayed_flush(struct apple_nvme *anv, struct nvme_ns *ns,
+ struct request *req)
+{
+ if (!anv->flush_interval || req_op(req) != REQ_OP_FLUSH)
+ return false;
+ if (delayed_work_pending(&anv->flush_dwork))
+ return true;
+ if (time_before(jiffies, anv->last_flush + anv->flush_interval)) {
+ kblockd_mod_delayed_work_on(WORK_CPU_UNBOUND, &anv->flush_dwork,
+ anv->flush_interval);
+ if (WARN_ON_ONCE(anv->flush_ns && anv->flush_ns != ns))
+ goto out;
+ anv->flush_ns = ns;
+ return true;
+ }
+out:
+ anv->last_flush = jiffies;
+ return false;
+}
+
static blk_status_t apple_nvme_queue_rq(struct blk_mq_hw_ctx *hctx,
const struct blk_mq_queue_data *bd)
{
@@ -764,6 +796,12 @@ static blk_status_t apple_nvme_queue_rq(struct blk_mq_hw_ctx *hctx,
}
nvme_start_request(req);
+
+ if (apple_nvme_delayed_flush(anv, ns, req)) {
+ blk_mq_complete_request(req);
+ return BLK_STS_OK;
+ }
+
apple_nvme_submit_cmd(q, cmnd);
return BLK_STS_OK;
@@ -1010,25 +1048,37 @@ static void apple_nvme_reset_work(struct work_struct *work)
ret = apple_rtkit_shutdown(anv->rtk);
if (ret)
goto out;
+
+ writel(0, anv->mmio_coproc + APPLE_ANS_COPROC_CPU_CONTROL);
}
- writel(0, anv->mmio_coproc + APPLE_ANS_COPROC_CPU_CONTROL);
+ /*
+ * Only do the soft-reset if the CPU is not running, which means either we
+ * or the previous stage shut it down cleanly.
+ */
+ if (!(readl(anv->mmio_coproc + APPLE_ANS_COPROC_CPU_CONTROL) &
+ APPLE_ANS_COPROC_CPU_CONTROL_RUN)) {
- ret = reset_control_assert(anv->reset);
- if (ret)
- goto out;
+ ret = reset_control_assert(anv->reset);
+ if (ret)
+ goto out;
- ret = apple_rtkit_reinit(anv->rtk);
- if (ret)
- goto out;
+ ret = apple_rtkit_reinit(anv->rtk);
+ if (ret)
+ goto out;
- ret = reset_control_deassert(anv->reset);
- if (ret)
- goto out;
+ ret = reset_control_deassert(anv->reset);
+ if (ret)
+ goto out;
- writel(APPLE_ANS_COPROC_CPU_CONTROL_RUN,
- anv->mmio_coproc + APPLE_ANS_COPROC_CPU_CONTROL);
- ret = apple_rtkit_boot(anv->rtk);
+ writel(APPLE_ANS_COPROC_CPU_CONTROL_RUN,
+ anv->mmio_coproc + APPLE_ANS_COPROC_CPU_CONTROL);
+
+ ret = apple_rtkit_boot(anv->rtk);
+ } else {
+ ret = apple_rtkit_wake(anv->rtk);
+ }
+
if (ret) {
dev_err(anv->dev, "ANS did not boot");
goto out;
@@ -1387,6 +1437,28 @@ static void devm_apple_nvme_mempool_destroy(void *data)
mempool_destroy(data);
}
+static void apple_nvme_flush_work(struct work_struct *work)
+{
+ struct nvme_command c = { };
+ struct apple_nvme *anv;
+ struct nvme_ns *ns;
+ int err;
+
+ anv = container_of(work, struct apple_nvme, flush_dwork.work);
+ ns = anv->flush_ns;
+ if (WARN_ON_ONCE(!ns))
+ return;
+
+ c.common.opcode = nvme_cmd_flush;
+ c.common.nsid = cpu_to_le32(anv->flush_ns->head->ns_id);
+ err = nvme_submit_sync_cmd(ns->queue, &c, NULL, 0);
+ if (err) {
+ dev_err(anv->dev, "Deferred flush failed: %d\n", err);
+ } else {
+ anv->last_flush = jiffies;
+ }
+}
+
static int apple_nvme_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -1521,12 +1593,21 @@ static int apple_nvme_probe(struct platform_device *pdev)
goto put_dev;
}
+ if (flush_interval) {
+ anv->flush_interval = msecs_to_jiffies(flush_interval);
+ anv->flush_ns = NULL;
+ anv->last_flush = jiffies - anv->flush_interval;
+ }
+
+ INIT_DELAYED_WORK(&anv->flush_dwork, apple_nvme_flush_work);
+
nvme_reset_ctrl(&anv->ctrl);
async_schedule(apple_nvme_async_probe, anv);
return 0;
put_dev:
+ apple_nvme_detach_genpd(anv);
put_device(anv->dev);
return ret;
}
@@ -1542,9 +1623,12 @@ static int apple_nvme_remove(struct platform_device *pdev)
apple_nvme_disable(anv, true);
nvme_uninit_ctrl(&anv->ctrl);
- if (apple_rtkit_is_running(anv->rtk))
+ if (apple_rtkit_is_running(anv->rtk)) {
apple_rtkit_shutdown(anv->rtk);
+ writel(0, anv->mmio_coproc + APPLE_ANS_COPROC_CPU_CONTROL);
+ }
+
apple_nvme_detach_genpd(anv);
return 0;
@@ -1554,9 +1638,13 @@ static void apple_nvme_shutdown(struct platform_device *pdev)
{
struct apple_nvme *anv = platform_get_drvdata(pdev);
+ flush_delayed_work(&anv->flush_dwork);
apple_nvme_disable(anv, true);
- if (apple_rtkit_is_running(anv->rtk))
+ if (apple_rtkit_is_running(anv->rtk)) {
apple_rtkit_shutdown(anv->rtk);
+
+ writel(0, anv->mmio_coproc + APPLE_ANS_COPROC_CPU_CONTROL);
+ }
}
static int apple_nvme_resume(struct device *dev)
@@ -1573,10 +1661,11 @@ static int apple_nvme_suspend(struct device *dev)
apple_nvme_disable(anv, true);
- if (apple_rtkit_is_running(anv->rtk))
+ if (apple_rtkit_is_running(anv->rtk)) {
ret = apple_rtkit_shutdown(anv->rtk);
- writel(0, anv->mmio_coproc + APPLE_ANS_COPROC_CPU_CONTROL);
+ writel(0, anv->mmio_coproc + APPLE_ANS_COPROC_CPU_CONTROL);
+ }
return ret;
}
diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig
index 5bc9c48..165cc32 100644
--- a/drivers/nvmem/Kconfig
+++ b/drivers/nvmem/Kconfig
@@ -298,6 +298,19 @@
This driver can also be built as a module. If so, the module
will be called nvmem-snvs-lpgpr.
+config NVMEM_SPMI_MFD
+ tristate "Generic SPMI MFD NVMEM"
+ depends on MFD_SIMPLE_MFD_SPMI || COMPILE_TEST
+ default ARCH_APPLE
+ help
+ Say y here to build a generic driver to expose an SPMI MFD device
+ as a NVMEM provider. This can be used for PMIC/PMU devices which
+ are used to store power and RTC-related settings on certain
+ platforms, such as Apple Silicon Macs.
+
+ This driver can also be built as a module. If so, the module
+ will be called nvmem-spmi-mfd.
+
config NVMEM_SPMI_SDAM
tristate "SPMI SDAM Support"
depends on SPMI
diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile
index 423baf0..eb86468 100644
--- a/drivers/nvmem/Makefile
+++ b/drivers/nvmem/Makefile
@@ -60,6 +60,8 @@
nvmem-sc27xx-efuse-y := sc27xx-efuse.o
obj-$(CONFIG_NVMEM_SNVS_LPGPR) += nvmem_snvs_lpgpr.o
nvmem_snvs_lpgpr-y := snvs_lpgpr.o
+obj-$(CONFIG_NVMEM_SPMI_MFD) += nvmem_spmi_mfd.o
+nvmem_spmi_mfd-y := spmi-mfd-nvmem.o
obj-$(CONFIG_NVMEM_SPMI_SDAM) += nvmem_qcom-spmi-sdam.o
nvmem_qcom-spmi-sdam-y += qcom-spmi-sdam.o
obj-$(CONFIG_NVMEM_SPRD_EFUSE) += nvmem_sprd_efuse.o
diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index bf42b7e..86482125 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -480,8 +480,8 @@ static int nvmem_cell_info_to_nvmem_cell_entry_nodup(struct nvmem_device *nvmem,
cell->np = info->np;
if (cell->nbits)
- cell->bytes = DIV_ROUND_UP(cell->nbits + cell->bit_offset,
- BITS_PER_BYTE);
+ cell->bytes = round_up(DIV_ROUND_UP(cell->nbits + cell->bit_offset,
+ BITS_PER_BYTE), nvmem->word_size);
if (!IS_ALIGNED(cell->offset, nvmem->stride)) {
dev_err(&nvmem->dev,
@@ -1557,15 +1557,23 @@ EXPORT_SYMBOL_GPL(nvmem_cell_put);
static void nvmem_shift_read_buffer_in_place(struct nvmem_cell_entry *cell, void *buf)
{
u8 *p, *b;
- int i, extra, bit_offset = cell->bit_offset;
+ int i, padding, extra, bit_offset = cell->bit_offset;
+ int bytes = cell->bytes;
p = b = buf;
if (bit_offset) {
+ padding = bit_offset/8;
+ if (padding) {
+ memmove(buf, buf + padding, bytes - padding);
+ bit_offset -= BITS_PER_BYTE * padding;
+ bytes -= padding;
+ }
+
/* First shift */
*b++ >>= bit_offset;
/* setup rest of the bytes if any */
- for (i = 1; i < cell->bytes; i++) {
+ for (i = 1; i < bytes; i++) {
/* Get bits from next byte and shift them towards msb */
*p |= *b << (BITS_PER_BYTE - bit_offset);
@@ -1578,7 +1586,7 @@ static void nvmem_shift_read_buffer_in_place(struct nvmem_cell_entry *cell, void
}
/* result fits in less bytes */
- extra = cell->bytes - DIV_ROUND_UP(cell->nbits, BITS_PER_BYTE);
+ extra = bytes - DIV_ROUND_UP(cell->nbits, BITS_PER_BYTE);
while (--extra >= 0)
*p-- = 0;
diff --git a/drivers/nvmem/spmi-mfd-nvmem.c b/drivers/nvmem/spmi-mfd-nvmem.c
new file mode 100644
index 0000000..e74ced4
--- /dev/null
+++ b/drivers/nvmem/spmi-mfd-nvmem.c
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Generic SPMI MFD NVMEM driver
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/nvmem-provider.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+struct spmi_mfd_nvmem {
+ struct regmap *regmap;
+ unsigned int base;
+};
+
+static int spmi_mfd_nvmem_read(void *priv, unsigned int offset,
+ void *val, size_t bytes)
+{
+ struct spmi_mfd_nvmem *nvmem = priv;
+
+ return regmap_bulk_read(nvmem->regmap, nvmem->base + offset, val, bytes);
+}
+
+static int spmi_mfd_nvmem_write(void *priv, unsigned int offset,
+ void *val, size_t bytes)
+{
+ struct spmi_mfd_nvmem *nvmem = priv;
+
+ return regmap_bulk_write(nvmem->regmap, nvmem->base + offset, val, bytes);
+}
+
+static int spmi_mfd_nvmem_probe(struct platform_device *pdev)
+{
+ struct spmi_mfd_nvmem *nvmem;
+ const __be32 *addr;
+ int len;
+ struct nvmem_config nvmem_cfg = {
+ .dev = &pdev->dev,
+ .name = "spmi_mfd_nvmem",
+ .id = NVMEM_DEVID_AUTO,
+ .word_size = 1,
+ .stride = 1,
+ .reg_read = spmi_mfd_nvmem_read,
+ .reg_write = spmi_mfd_nvmem_write,
+ };
+
+ nvmem = devm_kzalloc(&pdev->dev, sizeof(*nvmem), GFP_KERNEL);
+ if (!nvmem)
+ return -ENOMEM;
+
+ nvmem_cfg.priv = nvmem;
+
+ nvmem->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!nvmem->regmap) {
+ dev_err(&pdev->dev, "Parent regmap unavailable.\n");
+ return -ENXIO;
+ }
+
+ addr = of_get_property(pdev->dev.of_node, "reg", &len);
+ if (!addr) {
+ dev_err(&pdev->dev, "no reg property\n");
+ return -EINVAL;
+ }
+ if (len != 2 * sizeof(u32)) {
+ dev_err(&pdev->dev, "invalid reg property\n");
+ return -EINVAL;
+ }
+
+ nvmem->base = be32_to_cpup(&addr[0]);
+ nvmem_cfg.size = be32_to_cpup(&addr[1]);
+
+ return PTR_ERR_OR_ZERO(devm_nvmem_register(&pdev->dev, &nvmem_cfg));
+}
+
+static const struct of_device_id spmi_mfd_nvmem_id_table[] = {
+ { .compatible = "apple,spmi-pmu-nvmem" },
+ { .compatible = "spmi-mfd-nvmem" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, spmi_mfd_nvmem_id_table);
+
+static struct platform_driver spmi_mfd_nvmem_driver = {
+ .probe = spmi_mfd_nvmem_probe,
+ .driver = {
+ .name = "spmi-mfd-nvmem",
+ .of_match_table = spmi_mfd_nvmem_id_table,
+ },
+};
+
+module_platform_driver(spmi_mfd_nvmem_driver);
+
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
+MODULE_DESCRIPTION("SPMI MFD NVMEM driver");
diff --git a/drivers/of/address.c b/drivers/of/address.c
index b599563..4ebc861 100644
--- a/drivers/of/address.c
+++ b/drivers/of/address.c
@@ -554,7 +554,7 @@ static u64 __of_translate_address(struct device_node *dev,
pbus = of_match_bus(parent);
pbus->count_cells(dev, &pna, &pns);
if (!OF_CHECK_COUNTS(pna, pns)) {
- pr_err("Bad cell count for %pOF\n", dev);
+ pr_debug("Bad cell count for %pOF\n", dev);
break;
}
diff --git a/drivers/pci/controller/Kconfig b/drivers/pci/controller/Kconfig
index e534c02..efb5043 100644
--- a/drivers/pci/controller/Kconfig
+++ b/drivers/pci/controller/Kconfig
@@ -39,6 +39,7 @@
depends on ARCH_APPLE || COMPILE_TEST
depends on OF
depends on PCI_MSI
+ depends on ARM64_PAGE_SHIFT = 14 || COMPILE_TEST
select PCI_HOST_COMMON
help
Say Y here if you want to enable PCIe controller support on Apple
diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c
index f7a2483..13a95a7 100644
--- a/drivers/pci/controller/pcie-apple.c
+++ b/drivers/pci/controller/pcie-apple.c
@@ -27,9 +27,14 @@
#include <linux/module.h>
#include <linux/msi.h>
#include <linux/notifier.h>
+#include <linux/of_device.h>
#include <linux/of_irq.h>
#include <linux/pci-ecam.h>
+static int link_up_timeout = 500;
+module_param(link_up_timeout, int, 0644);
+MODULE_PARM_DESC(link_up_timeout, "PCIe link training timeout in milliseconds");
+
#define CORE_RC_PHYIF_CTL 0x00024
#define CORE_RC_PHYIF_CTL_RUN BIT(0)
#define CORE_RC_PHYIF_STAT 0x00028
@@ -40,14 +45,18 @@
#define CORE_RC_STAT_READY BIT(0)
#define CORE_FABRIC_STAT 0x04000
#define CORE_FABRIC_STAT_MASK 0x001F001F
-#define CORE_LANE_CFG(port) (0x84000 + 0x4000 * (port))
-#define CORE_LANE_CFG_REFCLK0REQ BIT(0)
-#define CORE_LANE_CFG_REFCLK1REQ BIT(1)
-#define CORE_LANE_CFG_REFCLK0ACK BIT(2)
-#define CORE_LANE_CFG_REFCLK1ACK BIT(3)
-#define CORE_LANE_CFG_REFCLKEN (BIT(9) | BIT(10))
-#define CORE_LANE_CTL(port) (0x84004 + 0x4000 * (port))
-#define CORE_LANE_CTL_CFGACC BIT(15)
+
+#define CORE_PHY_DEFAULT_BASE(port) (0x84000 + 0x4000 * (port))
+
+#define PHY_LANE_CFG 0x00000
+#define PHY_LANE_CFG_REFCLK0REQ BIT(0)
+#define PHY_LANE_CFG_REFCLK1REQ BIT(1)
+#define PHY_LANE_CFG_REFCLK0ACK BIT(2)
+#define PHY_LANE_CFG_REFCLK1ACK BIT(3)
+#define PHY_LANE_CFG_REFCLKEN (BIT(9) | BIT(10))
+#define PHY_LANE_CFG_REFCLKCGEN (BIT(30) | BIT(31))
+#define PHY_LANE_CTL 0x00004
+#define PHY_LANE_CTL_CFGACC BIT(15)
#define PORT_LTSSMCTL 0x00080
#define PORT_LTSSMCTL_START BIT(0)
@@ -101,7 +110,7 @@
#define PORT_REFCLK_CGDIS BIT(8)
#define PORT_PERST 0x00814
#define PORT_PERST_OFF BIT(0)
-#define PORT_RID2SID(i16) (0x00828 + 4 * (i16))
+#define PORT_RID2SID 0x00828
#define PORT_RID2SID_VALID BIT(31)
#define PORT_RID2SID_SID_SHIFT 16
#define PORT_RID2SID_BUS_SHIFT 8
@@ -119,7 +128,7 @@
#define PORT_TUNSTAT_PERST_ACK_PEND BIT(1)
#define PORT_PREFMEM_ENABLE 0x00994
-#define MAX_RID2SID 64
+#define MAX_RID2SID 512
/*
* The doorbell address is set to 0xfffff000, which by convention
@@ -130,6 +139,57 @@
*/
#define DOORBELL_ADDR CONFIG_PCIE_APPLE_MSI_DOORBELL_ADDR
+struct reg_info {
+ u32 phy_lane_ctl;
+ u32 port_msiaddr;
+ u32 port_msiaddr_hi;
+ u32 port_refclk;
+ u32 port_perst;
+ u32 port_rid2sid;
+ u32 port_msimap;
+ u32 max_rid2sid;
+ u32 max_msimap;
+};
+
+const struct reg_info t8103_hw = {
+ .phy_lane_ctl = PHY_LANE_CTL,
+ .port_msiaddr = PORT_MSIADDR,
+ .port_msiaddr_hi = 0,
+ .port_refclk = PORT_REFCLK,
+ .port_perst = PORT_PERST,
+ .port_rid2sid = PORT_RID2SID,
+ .port_msimap = 0,
+ .max_rid2sid = 64,
+ .max_msimap = 0,
+};
+
+#define PORT_T602X_MSIADDR 0x016c
+#define PORT_T602X_MSIADDR_HI 0x0170
+#define PORT_T602X_PERST 0x082c
+#define PORT_T602X_RID2SID 0x3000
+#define PORT_T602X_MSIMAP 0x3800
+
+#define PORT_MSIMAP_ENABLE BIT(31)
+#define PORT_MSIMAP_TARGET GENMASK(7, 0)
+
+const struct reg_info t602x_hw = {
+ .phy_lane_ctl = 0,
+ .port_msiaddr = PORT_T602X_MSIADDR,
+ .port_msiaddr_hi = PORT_T602X_MSIADDR_HI,
+ .port_refclk = 0,
+ .port_perst = PORT_T602X_PERST,
+ .port_rid2sid = PORT_T602X_RID2SID,
+ .port_msimap = PORT_T602X_MSIMAP,
+ .max_rid2sid = 512, /* 16 on t602x, guess for autodetect on future HW */
+ .max_msimap = 512, /* 96 on t602x, guess for autodetect on future HW */
+};
+
+static const struct of_device_id apple_pcie_of_match_hw[] = {
+ { .compatible = "apple,t6020-pcie", .data = &t602x_hw },
+ { .compatible = "apple,pcie", .data = &t8103_hw },
+ { }
+};
+
struct apple_pcie {
struct mutex lock;
struct device *dev;
@@ -140,12 +200,14 @@ struct apple_pcie {
struct completion event;
struct irq_fwspec fwspec;
u32 nvecs;
+ const struct reg_info *hw;
};
struct apple_pcie_port {
struct apple_pcie *pcie;
struct device_node *np;
void __iomem *base;
+ void __iomem *phy;
struct irq_domain *domain;
struct list_head entry;
DECLARE_BITMAP(sid_map, MAX_RID2SID);
@@ -262,14 +324,14 @@ static void apple_port_irq_mask(struct irq_data *data)
{
struct apple_pcie_port *port = irq_data_get_irq_chip_data(data);
- writel_relaxed(BIT(data->hwirq), port->base + PORT_INTMSKSET);
+ rmw_set(BIT(data->hwirq), port->base + PORT_INTMSK);
}
static void apple_port_irq_unmask(struct irq_data *data)
{
struct apple_pcie_port *port = irq_data_get_irq_chip_data(data);
- writel_relaxed(BIT(data->hwirq), port->base + PORT_INTMSKCLR);
+ rmw_clear(BIT(data->hwirq), port->base + PORT_INTMSK);
}
static bool hwirq_is_intx(unsigned int hwirq)
@@ -373,6 +435,7 @@ static void apple_port_irq_handler(struct irq_desc *desc)
static int apple_pcie_port_setup_irq(struct apple_pcie_port *port)
{
struct fwnode_handle *fwnode = &port->np->fwnode;
+ struct apple_pcie *pcie = port->pcie;
unsigned int irq;
/* FIXME: consider moving each interrupt under each port */
@@ -388,19 +451,35 @@ static int apple_pcie_port_setup_irq(struct apple_pcie_port *port)
return -ENOMEM;
/* Disable all interrupts */
- writel_relaxed(~0, port->base + PORT_INTMSKSET);
+ writel_relaxed(~0, port->base + PORT_INTMSK);
writel_relaxed(~0, port->base + PORT_INTSTAT);
+ writel_relaxed(~0, port->base + PORT_LINKCMDSTS);
irq_set_chained_handler_and_data(irq, apple_port_irq_handler, port);
/* Configure MSI base address */
- BUILD_BUG_ON(upper_32_bits(DOORBELL_ADDR));
- writel_relaxed(lower_32_bits(DOORBELL_ADDR), port->base + PORT_MSIADDR);
+ BUG_ON(upper_32_bits(DOORBELL_ADDR));
+ writel_relaxed(lower_32_bits(DOORBELL_ADDR),
+ port->base + pcie->hw->port_msiaddr);
+ if (pcie->hw->port_msiaddr_hi)
+ writel_relaxed(0, port->base + pcie->hw->port_msiaddr_hi);
/* Enable MSIs, shared between all ports */
- writel_relaxed(0, port->base + PORT_MSIBASE);
- writel_relaxed((ilog2(port->pcie->nvecs) << PORT_MSICFG_L2MSINUM_SHIFT) |
- PORT_MSICFG_EN, port->base + PORT_MSICFG);
+ if (pcie->hw->port_msimap) {
+ int i;
+
+ for (i = 0; i < pcie->nvecs; i++) {
+ writel_relaxed(FIELD_PREP(PORT_MSIMAP_TARGET, i) |
+ PORT_MSIMAP_ENABLE,
+ port->base + pcie->hw->port_msimap + 4 * i);
+ }
+
+ writel_relaxed(PORT_MSICFG_EN, port->base + PORT_MSICFG);
+ } else {
+ writel_relaxed(0, port->base + PORT_MSIBASE);
+ writel_relaxed((ilog2(pcie->nvecs) << PORT_MSICFG_L2MSINUM_SHIFT) |
+ PORT_MSICFG_EN, port->base + PORT_MSICFG);
+ }
return 0;
}
@@ -468,33 +547,32 @@ static int apple_pcie_setup_refclk(struct apple_pcie *pcie,
u32 stat;
int res;
- res = readl_relaxed_poll_timeout(pcie->base + CORE_RC_PHYIF_STAT, stat,
- stat & CORE_RC_PHYIF_STAT_REFCLK,
+ if (pcie->hw->phy_lane_ctl)
+ rmw_set(PHY_LANE_CTL_CFGACC, port->phy + pcie->hw->phy_lane_ctl);
+
+ rmw_set(PHY_LANE_CFG_REFCLK0REQ, port->phy + PHY_LANE_CFG);
+
+ res = readl_relaxed_poll_timeout(port->phy + PHY_LANE_CFG,
+ stat, stat & PHY_LANE_CFG_REFCLK0ACK,
100, 50000);
if (res < 0)
return res;
- rmw_set(CORE_LANE_CTL_CFGACC, pcie->base + CORE_LANE_CTL(port->idx));
- rmw_set(CORE_LANE_CFG_REFCLK0REQ, pcie->base + CORE_LANE_CFG(port->idx));
-
- res = readl_relaxed_poll_timeout(pcie->base + CORE_LANE_CFG(port->idx),
- stat, stat & CORE_LANE_CFG_REFCLK0ACK,
- 100, 50000);
- if (res < 0)
- return res;
-
- rmw_set(CORE_LANE_CFG_REFCLK1REQ, pcie->base + CORE_LANE_CFG(port->idx));
- res = readl_relaxed_poll_timeout(pcie->base + CORE_LANE_CFG(port->idx),
- stat, stat & CORE_LANE_CFG_REFCLK1ACK,
+ rmw_set(PHY_LANE_CFG_REFCLK1REQ, port->phy + PHY_LANE_CFG);
+ res = readl_relaxed_poll_timeout(port->phy + PHY_LANE_CFG,
+ stat, stat & PHY_LANE_CFG_REFCLK1ACK,
100, 50000);
if (res < 0)
return res;
- rmw_clear(CORE_LANE_CTL_CFGACC, pcie->base + CORE_LANE_CTL(port->idx));
+ if (pcie->hw->phy_lane_ctl)
+ rmw_clear(PHY_LANE_CTL_CFGACC, port->phy + pcie->hw->phy_lane_ctl);
- rmw_set(CORE_LANE_CFG_REFCLKEN, pcie->base + CORE_LANE_CFG(port->idx));
- rmw_set(PORT_REFCLK_EN, port->base + PORT_REFCLK);
+ rmw_set(PHY_LANE_CFG_REFCLKEN, port->phy + PHY_LANE_CFG);
+
+ if (pcie->hw->port_refclk)
+ rmw_set(PORT_REFCLK_EN, port->base + pcie->hw->port_refclk);
return 0;
}
@@ -502,9 +580,93 @@ static int apple_pcie_setup_refclk(struct apple_pcie *pcie,
static u32 apple_pcie_rid2sid_write(struct apple_pcie_port *port,
int idx, u32 val)
{
- writel_relaxed(val, port->base + PORT_RID2SID(idx));
+ writel_relaxed(val, port->base + port->pcie->hw->port_rid2sid + 4 * idx);
/* Read back to ensure completion of the write */
- return readl_relaxed(port->base + PORT_RID2SID(idx));
+ return readl_relaxed(port->base + port->pcie->hw->port_rid2sid + 4 * idx);
+}
+
+static int apple_pcie_probe_port(struct device_node *np)
+{
+ struct gpio_desc *gd;
+
+ gd = fwnode_gpiod_get_index(of_fwnode_handle(np), "reset", 0,
+ GPIOD_OUT_LOW, "PERST#");
+ if (IS_ERR(gd)) {
+ return PTR_ERR(gd);
+ }
+
+ gpiod_put(gd);
+
+ gd = fwnode_gpiod_get_index(of_fwnode_handle(np), "pwren", 0,
+ GPIOD_ASIS, "PWREN");
+ if (IS_ERR(gd)) {
+ if (PTR_ERR(gd) != -ENOENT)
+ return PTR_ERR(gd);
+ } else {
+ gpiod_put(gd);
+ }
+
+ return 0;
+}
+
+static int apple_pcie_setup_link(struct apple_pcie *pcie,
+ struct apple_pcie_port *port,
+ struct device_node *np)
+{
+ struct gpio_desc *reset, *pwren;
+ u32 stat;
+ int ret;
+
+ reset = devm_fwnode_gpiod_get(pcie->dev, of_fwnode_handle(np), "reset",
+ GPIOD_OUT_LOW, "PERST#");
+ if (IS_ERR(reset))
+ return PTR_ERR(reset);
+
+ pwren = devm_fwnode_gpiod_get(pcie->dev, of_fwnode_handle(np), "pwren",
+ GPIOD_ASIS, "PWREN");
+ if (IS_ERR(pwren)) {
+ if (PTR_ERR(pwren) == -ENOENT)
+ pwren = NULL;
+ else
+ return PTR_ERR(pwren);
+ }
+
+ rmw_set(PORT_APPCLK_EN, port->base + PORT_APPCLK);
+
+ /* Assert PERST# before setting up the clock */
+ gpiod_set_value_cansleep(reset, 1);
+
+ /* Power on the device if required */
+ gpiod_set_value_cansleep(pwren, 1);
+
+ ret = apple_pcie_setup_refclk(pcie, port);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * The minimal Tperst-clk value is 100us (PCIe CEM r5.0, 2.9.2)
+ * If powering up, the minimal Tpvperl is 100ms
+ */
+ if (pwren)
+ msleep(100);
+ else
+ usleep_range(100, 200);
+
+ /* Deassert PERST# */
+ rmw_set(PORT_PERST_OFF, port->base + pcie->hw->port_perst);
+ gpiod_set_value_cansleep(reset, 0);
+
+ /* Wait for 100ms after PERST# deassertion (PCIe r5.0, 6.6.1) */
+ msleep(100);
+
+ ret = readl_relaxed_poll_timeout(port->base + PORT_STATUS, stat,
+ stat & PORT_STATUS_READY, 100, 250000);
+ if (ret < 0) {
+ dev_err(pcie->dev, "port %pOF ready wait timeout\n", np);
+ return ret;
+ }
+
+ return 0;
}
static int apple_pcie_setup_port(struct apple_pcie *pcie,
@@ -512,14 +674,9 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie,
{
struct platform_device *platform = to_platform_device(pcie->dev);
struct apple_pcie_port *port;
- struct gpio_desc *reset;
- u32 stat, idx;
+ u32 link_stat, idx;
int ret, i;
-
- reset = devm_fwnode_gpiod_get(pcie->dev, of_fwnode_handle(np), "reset",
- GPIOD_OUT_LOW, "PERST#");
- if (IS_ERR(reset))
- return PTR_ERR(reset);
+ char name[16];
port = devm_kzalloc(pcie->dev, sizeof(*port), GFP_KERNEL);
if (!port)
@@ -534,45 +691,33 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie,
port->pcie = pcie;
port->np = np;
- port->base = devm_platform_ioremap_resource(platform, port->idx + 2);
+ snprintf(name, sizeof(name), "port%d", port->idx);
+ port->base = devm_platform_ioremap_resource_byname(platform, name);
if (IS_ERR(port->base))
+ port->base = devm_platform_ioremap_resource(platform, port->idx + 2);
+ if (IS_ERR(port->base)) {
return PTR_ERR(port->base);
-
- rmw_set(PORT_APPCLK_EN, port->base + PORT_APPCLK);
-
- /* Assert PERST# before setting up the clock */
- gpiod_set_value(reset, 1);
-
- ret = apple_pcie_setup_refclk(pcie, port);
- if (ret < 0)
- return ret;
-
- /* The minimal Tperst-clk value is 100us (PCIe CEM r5.0, 2.9.2) */
- usleep_range(100, 200);
-
- /* Deassert PERST# */
- rmw_set(PORT_PERST_OFF, port->base + PORT_PERST);
- gpiod_set_value(reset, 0);
-
- /* Wait for 100ms after PERST# deassertion (PCIe r5.0, 6.6.1) */
- msleep(100);
-
- ret = readl_relaxed_poll_timeout(port->base + PORT_STATUS, stat,
- stat & PORT_STATUS_READY, 100, 250000);
- if (ret < 0) {
- dev_err(pcie->dev, "port %pOF ready wait timeout\n", np);
- return ret;
}
- rmw_clear(PORT_REFCLK_CGDIS, port->base + PORT_REFCLK);
- rmw_clear(PORT_APPCLK_CGDIS, port->base + PORT_APPCLK);
+ snprintf(name, sizeof(name), "phy%d", port->idx);
+ port->phy = devm_platform_ioremap_resource_byname(platform, name);
+ if (IS_ERR(port->phy))
+ port->phy = pcie->base + CORE_PHY_DEFAULT_BASE(port->idx);
+
+ /* link might be already brought up by u-boot, skip setup then */
+ link_stat = readl_relaxed(port->base + PORT_LINKSTS);
+ if (!(link_stat & PORT_LINKSTS_UP)) {
+ ret = apple_pcie_setup_link(pcie, port, np);
+ if (ret)
+ return ret;
+ }
ret = apple_pcie_port_setup_irq(port);
if (ret)
return ret;
/* Reset all RID/SID mappings, and check for RAZ/WI registers */
- for (i = 0; i < MAX_RID2SID; i++) {
+ for (i = 0; i < pcie->hw->max_rid2sid; i++) {
if (apple_pcie_rid2sid_write(port, i, 0xbad1d) != 0xbad1d)
break;
apple_pcie_rid2sid_write(port, i, 0);
@@ -585,13 +730,33 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie,
list_add_tail(&port->entry, &pcie->ports);
init_completion(&pcie->event);
+ /* In the success path, we keep a reference to np around */
+ of_node_get(np);
+
ret = apple_pcie_port_register_irqs(port);
WARN_ON(ret);
- writel_relaxed(PORT_LTSSMCTL_START, port->base + PORT_LTSSMCTL);
+ link_stat = readl_relaxed(port->base + PORT_LINKSTS);
+ if (!(link_stat & PORT_LINKSTS_UP)) {
+ unsigned long timeout, left;
+ /* start link training */
+ writel_relaxed(PORT_LTSSMCTL_START, port->base + PORT_LTSSMCTL);
- if (!wait_for_completion_timeout(&pcie->event, HZ / 10))
- dev_warn(pcie->dev, "%pOF link didn't come up\n", np);
+ timeout = link_up_timeout * HZ / 1000;
+ left = wait_for_completion_timeout(&pcie->event, timeout);
+ if (!left)
+ dev_warn(pcie->dev, "%pOF link didn't come up\n", np);
+ else
+ dev_info(pcie->dev, "%pOF link up after %ldms\n", np,
+ (timeout - left) * 1000 / HZ);
+
+ }
+
+ if (pcie->hw->port_refclk)
+ rmw_clear(PORT_REFCLK_CGDIS, port->base + PORT_REFCLK);
+ else
+ rmw_set(PHY_LANE_CFG_REFCLKCGEN, port->phy + PHY_LANE_CFG);
+ rmw_clear(PORT_APPCLK_CGDIS, port->base + PORT_APPCLK);
return 0;
}
@@ -709,7 +874,7 @@ static void apple_pcie_release_device(struct apple_pcie_port *port,
for_each_set_bit(idx, port->sid_map, port->sid_map_sz) {
u32 val;
- val = readl_relaxed(port->base + PORT_RID2SID(idx));
+ val = readl_relaxed(port->base + port->pcie->hw->port_rid2sid + 4 * idx);
if ((val & 0xffff) == rid) {
apple_pcie_rid2sid_write(port, idx, 0);
bitmap_release_region(port->sid_map, idx, 0);
@@ -766,13 +931,19 @@ static int apple_pcie_init(struct pci_config_window *cfg)
struct platform_device *platform = to_platform_device(dev);
struct device_node *of_port;
struct apple_pcie *pcie;
+ const struct of_device_id *match;
int ret;
+ match = of_match_device(apple_pcie_of_match_hw, dev);
+ if (!match)
+ return -ENODEV;
+
pcie = devm_kzalloc(dev, sizeof(*pcie), GFP_KERNEL);
if (!pcie)
return -ENOMEM;
pcie->dev = dev;
+ pcie->hw = match->data;
mutex_init(&pcie->lock);
@@ -787,7 +958,7 @@ static int apple_pcie_init(struct pci_config_window *cfg)
if (ret)
return ret;
- for_each_child_of_node(dev->of_node, of_port) {
+ for_each_available_child_of_node(dev->of_node, of_port) {
ret = apple_pcie_setup_port(pcie, of_port);
if (ret) {
dev_err(pcie->dev, "Port %pOF setup fail: %d\n", of_port, ret);
@@ -801,8 +972,19 @@ static int apple_pcie_init(struct pci_config_window *cfg)
static int apple_pcie_probe(struct platform_device *pdev)
{
+ struct device *dev = &pdev->dev;
+ struct device_node *of_port;
int ret;
+ /* Check for probe dependencies for all ports first */
+ for_each_available_child_of_node(dev->of_node, of_port) {
+ ret = apple_pcie_probe_port(of_port);
+ if (ret) {
+ of_node_put(of_port);
+ return dev_err_probe(dev, ret, "Port %pOF probe fail\n", of_port);
+ }
+ }
+
ret = bus_register_notifier(&pci_bus_type, &apple_pcie_nb);
if (ret)
return ret;
@@ -824,6 +1006,7 @@ static const struct pci_ecam_ops apple_pcie_cfg_ecam_ops = {
};
static const struct of_device_id apple_pcie_of_match[] = {
+ { .compatible = "apple,t6020-pcie", .data = &apple_pcie_cfg_ecam_ops },
{ .compatible = "apple,pcie", .data = &apple_pcie_cfg_ecam_ops },
{ }
};
diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
index 787354b..97598e1 100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -74,6 +74,7 @@
source "drivers/phy/allwinner/Kconfig"
source "drivers/phy/amlogic/Kconfig"
+source "drivers/phy/apple/Kconfig"
source "drivers/phy/broadcom/Kconfig"
source "drivers/phy/cadence/Kconfig"
source "drivers/phy/freescale/Kconfig"
diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index 868a220..6b2764f 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -12,6 +12,7 @@
obj-$(CONFIG_USB_LGM_PHY) += phy-lgm-usb.o
obj-y += allwinner/ \
amlogic/ \
+ apple/ \
broadcom/ \
cadence/ \
freescale/ \
diff --git a/drivers/phy/apple/Kconfig b/drivers/phy/apple/Kconfig
new file mode 100644
index 0000000..090d854
--- /dev/null
+++ b/drivers/phy/apple/Kconfig
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+config PHY_APPLE_ATC
+ tristate "Apple Type-C PHY"
+ depends on ARCH_APPLE || COMPILE_TEST
+ default ARCH_APPLE
+ select GENERIC_PHY
+ select TYPEC
+ help
+ Enable this to add support for the Apple Type-C PHY, switch
+ and mux found in Apple SoCs such as the M1.
+ This driver currently provides support for USB2 and USB3.
diff --git a/drivers/phy/apple/Makefile b/drivers/phy/apple/Makefile
new file mode 100644
index 0000000..af863fa
--- /dev/null
+++ b/drivers/phy/apple/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+CFLAGS_trace.o := -I$(src)
+
+obj-$(CONFIG_PHY_APPLE_ATC) += phy-apple-atc.o
+phy-apple-atc-y := atc.o
+phy-apple-atc-$(CONFIG_TRACING) += trace.o
diff --git a/drivers/phy/apple/atc.c b/drivers/phy/apple/atc.c
new file mode 100644
index 0000000..8397227
--- /dev/null
+++ b/drivers/phy/apple/atc.c
@@ -0,0 +1,2404 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+/*
+ * Apple Type-C PHY driver
+ *
+ * Copyright (C) The Asahi Linux Contributors
+ * Author: Sven Peter <sven@svenpeter.dev>
+ */
+
+#include "atc.h"
+#include "trace.h"
+
+#include <dt-bindings/phy/phy.h>
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/reset-controller.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/types.h>
+#include <linux/usb/typec.h>
+#include <linux/usb/typec_altmode.h>
+#include <linux/usb/typec_dp.h>
+#include <linux/usb/typec_mux.h>
+#include <linux/usb/typec_tbt.h>
+
+#define rcdev_to_apple_atcphy(_rcdev) \
+ container_of(_rcdev, struct apple_atcphy, rcdev)
+
+#define AUSPLL_APB_CMD_OVERRIDE 0x2000
+#define AUSPLL_APB_CMD_OVERRIDE_REQ BIT(0)
+#define AUSPLL_APB_CMD_OVERRIDE_ACK BIT(1)
+#define AUSPLL_APB_CMD_OVERRIDE_UNK28 BIT(28)
+#define AUSPLL_APB_CMD_OVERRIDE_CMD GENMASK(27, 3)
+
+#define AUSPLL_FREQ_DESC_A 0x2080
+#define AUSPLL_FD_FREQ_COUNT_TARGET GENMASK(9, 0)
+#define AUSPLL_FD_FBDIVN_HALF BIT(10)
+#define AUSPLL_FD_REV_DIVN GENMASK(13, 11)
+#define AUSPLL_FD_KI_MAN GENMASK(17, 14)
+#define AUSPLL_FD_KI_EXP GENMASK(21, 18)
+#define AUSPLL_FD_KP_MAN GENMASK(25, 22)
+#define AUSPLL_FD_KP_EXP GENMASK(29, 26)
+#define AUSPLL_FD_KPKI_SCALE_HBW GENMASK(31, 30)
+
+#define AUSPLL_FREQ_DESC_B 0x2084
+#define AUSPLL_FD_FBDIVN_FRAC_DEN GENMASK(13, 0)
+#define AUSPLL_FD_FBDIVN_FRAC_NUM GENMASK(27, 14)
+
+#define AUSPLL_FREQ_DESC_C 0x2088
+#define AUSPLL_FD_SDM_SSC_STEP GENMASK(7, 0)
+#define AUSPLL_FD_SDM_SSC_EN BIT(8)
+#define AUSPLL_FD_PCLK_DIV_SEL GENMASK(13, 9)
+#define AUSPLL_FD_LFSDM_DIV GENMASK(15, 14)
+#define AUSPLL_FD_LFCLK_CTRL GENMASK(19, 16)
+#define AUSPLL_FD_VCLK_OP_DIVN GENMASK(21, 20)
+#define AUSPLL_FD_VCLK_PRE_DIVN BIT(22)
+
+#define AUSPLL_DCO_EFUSE_SPARE 0x222c
+#define AUSPLL_RODCO_ENCAP_EFUSE GENMASK(10, 9)
+#define AUSPLL_RODCO_BIAS_ADJUST_EFUSE GENMASK(14, 12)
+
+#define AUSPLL_FRACN_CAN 0x22a4
+#define AUSPLL_DLL_START_CAPCODE GENMASK(18, 17)
+
+#define AUSPLL_CLKOUT_MASTER 0x2200
+#define AUSPLL_CLKOUT_MASTER_PCLK_DRVR_EN BIT(2)
+#define AUSPLL_CLKOUT_MASTER_PCLK2_DRVR_EN BIT(4)
+#define AUSPLL_CLKOUT_MASTER_REFBUFCLK_DRVR_EN BIT(6)
+
+#define AUSPLL_CLKOUT_DIV 0x2208
+#define AUSPLL_CLKOUT_PLLA_REFBUFCLK_DI GENMASK(20, 16)
+
+#define AUSPLL_BGR 0x2214
+#define AUSPLL_BGR_CTRL_AVAIL BIT(0)
+
+#define AUSPLL_CLKOUT_DTC_VREG 0x2220
+#define AUSPLL_DTC_VREG_ADJUST GENMASK(16, 14)
+#define AUSPLL_DTC_VREG_BYPASS BIT(7)
+
+#define AUSPLL_FREQ_CFG 0x2224
+#define AUSPLL_FREQ_REFCLK GENMASK(1, 0)
+
+#define AUS_COMMON_SHIM_BLK_VREG 0x0a04
+#define AUS_VREG_TRIM GENMASK(6, 2)
+
+#define CIO3PLL_CLK_CTRL 0x2a00
+#define CIO3PLL_CLK_PCLK_EN BIT(1)
+#define CIO3PLL_CLK_REFCLK_EN BIT(5)
+
+#define CIO3PLL_DCO_NCTRL 0x2a38
+#define CIO3PLL_DCO_COARSEBIN_EFUSE0 GENMASK(6, 0)
+#define CIO3PLL_DCO_COARSEBIN_EFUSE1 GENMASK(23, 17)
+
+#define CIO3PLL_FRACN_CAN 0x2aa4
+#define CIO3PLL_DLL_CAL_START_CAPCODE GENMASK(18, 17)
+
+#define CIO3PLL_DTC_VREG 0x2a20
+#define CIO3PLL_DTC_VREG_ADJUST GENMASK(16, 14)
+
+#define ACIOPHY_CROSSBAR 0x4c
+#define ACIOPHY_CROSSBAR_PROTOCOL GENMASK(4, 0)
+#define ACIOPHY_CROSSBAR_PROTOCOL_USB4 0x0
+#define ACIOPHY_CROSSBAR_PROTOCOL_USB4_SWAPPED 0x1
+#define ACIOPHY_CROSSBAR_PROTOCOL_USB3 0xa
+#define ACIOPHY_CROSSBAR_PROTOCOL_USB3_SWAPPED 0xb
+#define ACIOPHY_CROSSBAR_PROTOCOL_USB3_DP 0x10
+#define ACIOPHY_CROSSBAR_PROTOCOL_USB3_DP_SWAPPED 0x11
+#define ACIOPHY_CROSSBAR_PROTOCOL_DP 0x14
+#define ACIOPHY_CROSSBAR_DP_SINGLE_PMA GENMASK(16, 5)
+#define ACIOPHY_CROSSBAR_DP_SINGLE_PMA_NONE 0x0000
+#define ACIOPHY_CROSSBAR_DP_SINGLE_PMA_UNK100 0x100
+#define ACIOPHY_CROSSBAR_DP_SINGLE_PMA_UNK008 0x008
+#define ACIOPHY_CROSSBAR_DP_BOTH_PMA BIT(17)
+
+#define ACIOPHY_LANE_MODE 0x48
+#define ACIOPHY_LANE_MODE_RX0 GENMASK(2, 0)
+#define ACIOPHY_LANE_MODE_TX0 GENMASK(5, 3)
+#define ACIOPHY_LANE_MODE_RX1 GENMASK(8, 6)
+#define ACIOPHY_LANE_MODE_TX1 GENMASK(11, 9)
+#define ACIOPHY_LANE_MODE_USB4 0
+#define ACIOPHY_LANE_MODE_USB3 1
+#define ACIOPHY_LANE_MODE_DP 2
+#define ACIOPHY_LANE_MODE_OFF 3
+
+#define ACIOPHY_TOP_BIST_CIOPHY_CFG1 0x84
+#define ACIOPHY_TOP_BIST_CIOPHY_CFG1_CLK_EN BIT(27)
+#define ACIOPHY_TOP_BIST_CIOPHY_CFG1_BIST_EN BIT(28)
+
+#define ACIOPHY_TOP_BIST_OV_CFG 0x8c
+#define ACIOPHY_TOP_BIST_OV_CFG_LN0_RESET_N_OV BIT(13)
+#define ACIOPHY_TOP_BIST_OV_CFG_LN0_PWR_DOWN_OV BIT(25)
+
+#define ACIOPHY_TOP_BIST_READ_CTRL 0x90
+#define ACIOPHY_TOP_BIST_READ_CTRL_LN0_PHY_STATUS_RE BIT(2)
+
+#define ACIOPHY_TOP_PHY_STAT 0x9c
+#define ACIOPHY_TOP_PHY_STAT_LN0_UNK0 BIT(0)
+#define ACIOPHY_TOP_PHY_STAT_LN0_UNK23 BIT(23)
+
+#define ACIOPHY_TOP_BIST_PHY_CFG0 0xa8
+#define ACIOPHY_TOP_BIST_PHY_CFG0_LN0_RESET_N BIT(0)
+
+#define ACIOPHY_TOP_BIST_PHY_CFG1 0xac
+#define ACIOPHY_TOP_BIST_PHY_CFG1_LN0_PWR_DOWN GENMASK(13, 10)
+
+#define ACIOPHY_PLL_COMMON_CTRL 0x1028
+#define ACIOPHY_PLL_WAIT_FOR_CMN_READY_BEFORE_RESET_EXIT BIT(24)
+
+#define ATCPHY_POWER_CTRL 0x20000
+#define ATCPHY_POWER_STAT 0x20004
+#define ATCPHY_POWER_SLEEP_SMALL BIT(0)
+#define ATCPHY_POWER_SLEEP_BIG BIT(1)
+#define ATCPHY_POWER_CLAMP_EN BIT(2)
+#define ATCPHY_POWER_APB_RESET_N BIT(3)
+#define ATCPHY_POWER_PHY_RESET_N BIT(4)
+
+#define ATCPHY_MISC 0x20008
+#define ATCPHY_MISC_RESET_N BIT(0)
+#define ATCPHY_MISC_LANE_SWAP BIT(2)
+
+#define ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0 0x7000
+#define DP_PMA_BYTECLK_RESET BIT(0)
+#define DP_MAC_DIV20_CLK_SEL BIT(1)
+#define DPTXPHY_PMA_LANE_RESET_N BIT(2)
+#define DPTXPHY_PMA_LANE_RESET_N_OV BIT(3)
+#define DPTX_PCLK1_SELECT GENMASK(6, 4)
+#define DPTX_PCLK2_SELECT GENMASK(9, 7)
+#define DPRX_PCLK_SELECT GENMASK(12, 10)
+#define DPTX_PCLK1_ENABLE BIT(13)
+#define DPTX_PCLK2_ENABLE BIT(14)
+#define DPRX_PCLK_ENABLE BIT(15)
+
+#define ACIOPHY_DP_PCLK_STAT 0x7044
+#define ACIOPHY_AUSPLL_LOCK BIT(3)
+
+#define LN0_AUSPMA_RX_TOP 0x9000
+#define LN0_AUSPMA_RX_EQ 0xA000
+#define LN0_AUSPMA_RX_SHM 0xB000
+#define LN0_AUSPMA_TX_TOP 0xC000
+#define LN0_AUSPMA_TX_SHM 0xD000
+
+#define LN1_AUSPMA_RX_TOP 0x10000
+#define LN1_AUSPMA_RX_EQ 0x11000
+#define LN1_AUSPMA_RX_SHM 0x12000
+#define LN1_AUSPMA_TX_TOP 0x13000
+#define LN1_AUSPMA_TX_SHM 0x14000
+
+#define LN_AUSPMA_RX_TOP_PMAFSM 0x0010
+#define LN_AUSPMA_RX_TOP_PMAFSM_PCS_OV BIT(0)
+#define LN_AUSPMA_RX_TOP_PMAFSM_PCS_REQ BIT(9)
+
+#define LN_AUSPMA_RX_TOP_TJ_CFG_RX_TXMODE 0x00F0
+#define LN_RX_TXMODE BIT(0)
+
+#define LN_AUSPMA_RX_SHM_TJ_RXA_CTLE_CTRL0 0x00
+#define LN_TX_CLK_EN BIT(20)
+#define LN_TX_CLK_EN_OV BIT(21)
+
+#define LN_AUSPMA_RX_SHM_TJ_RXA_AFE_CTRL1 0x04
+#define LN_RX_DIV20_RESET_N_OV BIT(29)
+#define LN_RX_DIV20_RESET_N BIT(30)
+
+#define LN_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL2 0x08
+#define LN_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL3 0x0C
+#define LN_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL4 0x10
+#define LN_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL5 0x14
+#define LN_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL6 0x18
+#define LN_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL7 0x1C
+#define LN_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL8 0x20
+#define LN_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL9 0x24
+#define LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL10 0x28
+#define LN_DTVREG_ADJUST GENMASK(31, 27)
+
+#define LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL11 0x2C
+#define LN_DTVREG_BIG_EN BIT(23)
+#define LN_DTVREG_BIG_EN_OV BIT(24)
+#define LN_DTVREG_SML_EN BIT(25)
+#define LN_DTVREG_SML_EN_OV BIT(26)
+
+#define LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL12 0x30
+#define LN_TX_BYTECLK_RESET_SYNC_CLR BIT(22)
+#define LN_TX_BYTECLK_RESET_SYNC_CLR_OV BIT(23)
+#define LN_TX_BYTECLK_RESET_SYNC_EN BIT(24)
+#define LN_TX_BYTECLK_RESET_SYNC_EN_OV BIT(25)
+#define LN_TX_HRCLK_SEL BIT(28)
+#define LN_TX_HRCLK_SEL_OV BIT(29)
+#define LN_TX_PBIAS_EN BIT(30)
+#define LN_TX_PBIAS_EN_OV BIT(31)
+
+#define LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL13 0x34
+#define LN_TX_PRE_EN BIT(0)
+#define LN_TX_PRE_EN_OV BIT(1)
+#define LN_TX_PST1_EN BIT(2)
+#define LN_TX_PST1_EN_OV BIT(3)
+#define LN_DTVREG_ADJUST_OV BIT(15)
+
+#define LN_AUSPMA_RX_SHM_TJ_UNK_CTRL14A 0x38
+#define LN_AUSPMA_RX_SHM_TJ_UNK_CTRL14B 0x3C
+#define LN_AUSPMA_RX_SHM_TJ_UNK_CTRL15A 0x40
+#define LN_AUSPMA_RX_SHM_TJ_UNK_CTRL15B 0x44
+#define LN_AUSPMA_RX_SHM_TJ_RXA_SAVOS_CTRL16 0x48
+#define LN_RXTERM_EN BIT(21)
+#define LN_RXTERM_EN_OV BIT(22)
+#define LN_RXTERM_PULLUP_LEAK_EN BIT(23)
+#define LN_RXTERM_PULLUP_LEAK_EN_OV BIT(24)
+#define LN_TX_CAL_CODE GENMASK(29, 25)
+#define LN_TX_CAL_CODE_OV BIT(30)
+
+#define LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL17 0x4C
+#define LN_TX_MARGIN GENMASK(19, 15)
+#define LN_TX_MARGIN_OV BIT(20)
+#define LN_TX_MARGIN_LSB BIT(21)
+#define LN_TX_MARGIN_LSB_OV BIT(22)
+#define LN_TX_MARGIN_P1 GENMASK(26, 23)
+#define LN_TX_MARGIN_P1_OV BIT(27)
+#define LN_TX_MARGIN_P1_LSB GENMASK(29, 28)
+#define LN_TX_MARGIN_P1_LSB_OV BIT(30)
+
+#define LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18 0x50
+#define LN_TX_P1_CODE GENMASK(3, 0)
+#define LN_TX_P1_CODE_OV BIT(4)
+#define LN_TX_P1_LSB_CODE GENMASK(6, 5)
+#define LN_TX_P1_LSB_CODE_OV BIT(7)
+#define LN_TX_MARGIN_PRE GENMASK(10, 8)
+#define LN_TX_MARGIN_PRE_OV BIT(11)
+#define LN_TX_MARGIN_PRE_LSB GENMASK(13, 12)
+#define LN_TX_MARGIN_PRE_LSB_OV BIT(14)
+#define LN_TX_PRE_LSB_CODE GENMASK(16, 15)
+#define LN_TX_PRE_LSB_CODE_OV BIT(17)
+#define LN_TX_PRE_CODE GENMASK(21, 18)
+#define LN_TX_PRE_CODE_OV BIT(22)
+
+#define LN_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19 0x54
+#define LN_TX_TEST_EN BIT(21)
+#define LN_TX_TEST_EN_OV BIT(22)
+#define LN_TX_EN BIT(23)
+#define LN_TX_EN_OV BIT(24)
+#define LN_TX_CLK_DLY_CTRL_TAPGEN GENMASK(27, 25)
+#define LN_TX_CLK_DIV2_EN BIT(28)
+#define LN_TX_CLK_DIV2_EN_OV BIT(29)
+#define LN_TX_CLK_DIV2_RST BIT(30)
+#define LN_TX_CLK_DIV2_RST_OV BIT(31)
+
+#define LN_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL20 0x58
+#define LN_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL21 0x5C
+#define LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22 0x60
+#define LN_VREF_ADJUST_GRAY GENMASK(11, 7)
+#define LN_VREF_ADJUST_GRAY_OV BIT(12)
+#define LN_VREF_BIAS_SEL GENMASK(14, 13)
+#define LN_VREF_BIAS_SEL_OV BIT(15)
+#define LN_VREF_BOOST_EN BIT(16)
+#define LN_VREF_BOOST_EN_OV BIT(17)
+#define LN_VREF_EN BIT(18)
+#define LN_VREF_EN_OV BIT(19)
+#define LN_VREF_LPBKIN_DATA GENMASK(29, 28)
+#define LN_VREF_TEST_RXLPBKDT_EN BIT(30)
+#define LN_VREF_TEST_RXLPBKDT_EN_OV BIT(31)
+
+#define LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG0 0x00
+#define LN_BYTECLK_RESET_SYNC_EN_OV BIT(2)
+#define LN_BYTECLK_RESET_SYNC_EN BIT(3)
+#define LN_BYTECLK_RESET_SYNC_CLR_OV BIT(4)
+#define LN_BYTECLK_RESET_SYNC_CLR BIT(5)
+#define LN_BYTECLK_RESET_SYNC_SEL_OV BIT(6)
+
+#define LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG1 0x04
+#define LN_TXA_DIV2_EN_OV BIT(8)
+#define LN_TXA_DIV2_EN BIT(9)
+#define LN_TXA_DIV2_RESET_OV BIT(10)
+#define LN_TXA_DIV2_RESET BIT(11)
+#define LN_TXA_CLK_EN_OV BIT(22)
+#define LN_TXA_CLK_EN BIT(23)
+
+#define LN_AUSPMA_TX_SHM_TXA_IMP_REG0 0x08
+#define LN_TXA_CAL_CTRL_OV BIT(0)
+#define LN_TXA_CAL_CTRL GENMASK(18, 1)
+#define LN_TXA_CAL_CTRL_BASE_OV BIT(19)
+#define LN_TXA_CAL_CTRL_BASE GENMASK(23, 20)
+#define LN_TXA_HIZ_OV BIT(29)
+#define LN_TXA_HIZ BIT(30)
+
+#define LN_AUSPMA_TX_SHM_TXA_IMP_REG1 0x0C
+#define LN_AUSPMA_TX_SHM_TXA_IMP_REG2 0x10
+#define LN_TXA_MARGIN_OV BIT(0)
+#define LN_TXA_MARGIN GENMASK(18, 1)
+#define LN_TXA_MARGIN_2R_OV BIT(19)
+#define LN_TXA_MARGIN_2R BIT(20)
+
+#define LN_AUSPMA_TX_SHM_TXA_IMP_REG3 0x14
+#define LN_TXA_MARGIN_POST_OV BIT(0)
+#define LN_TXA_MARGIN_POST GENMASK(10, 1)
+#define LN_TXA_MARGIN_POST_2R_OV BIT(11)
+#define LN_TXA_MARGIN_POST_2R BIT(12)
+#define LN_TXA_MARGIN_POST_4R_OV BIT(13)
+#define LN_TXA_MARGIN_POST_4R BIT(14)
+#define LN_TXA_MARGIN_PRE_OV BIT(15)
+#define LN_TXA_MARGIN_PRE GENMASK(21, 16)
+#define LN_TXA_MARGIN_PRE_2R_OV BIT(22)
+#define LN_TXA_MARGIN_PRE_2R BIT(23)
+#define LN_TXA_MARGIN_PRE_4R_OV BIT(24)
+#define LN_TXA_MARGIN_PRE_4R BIT(25)
+
+#define LN_AUSPMA_TX_SHM_TXA_UNK_REG0 0x18
+#define LN_AUSPMA_TX_SHM_TXA_UNK_REG1 0x1C
+#define LN_AUSPMA_TX_SHM_TXA_UNK_REG2 0x20
+
+#define LN_AUSPMA_TX_SHM_TXA_LDOCLK 0x24
+#define LN_LDOCLK_BYPASS_SML_OV BIT(8)
+#define LN_LDOCLK_BYPASS_SML BIT(9)
+#define LN_LDOCLK_BYPASS_BIG_OV BIT(10)
+#define LN_LDOCLK_BYPASS_BIG BIT(11)
+#define LN_LDOCLK_EN_SML_OV BIT(12)
+#define LN_LDOCLK_EN_SML BIT(13)
+#define LN_LDOCLK_EN_BIG_OV BIT(14)
+#define LN_LDOCLK_EN_BIG BIT(15)
+
+/* LPDPTX registers */
+#define LPDPTX_AUX_CFG_BLK_AUX_CTRL 0x0000
+#define LPDPTX_BLK_AUX_CTRL_PWRDN BIT(4)
+#define LPDPTX_BLK_AUX_RXOFFSET GENMASK(25, 22)
+
+#define LPDPTX_AUX_CFG_BLK_AUX_LDO_CTRL 0x0008
+
+#define LPDPTX_AUX_CFG_BLK_AUX_MARGIN 0x000c
+#define LPDPTX_MARGIN_RCAL_RXOFFSET_EN BIT(5)
+#define LPDPTX_AUX_MARGIN_RCAL_TXSWING GENMASK(10, 6)
+
+#define LPDPTX_AUX_SHM_CFG_BLK_AUX_CTRL_REG0 0x0204
+#define LPDPTX_CFG_PMA_AUX_SEL_LF_DATA BIT(15)
+
+#define LPDPTX_AUX_SHM_CFG_BLK_AUX_CTRL_REG1 0x0208
+#define LPDPTX_CFG_PMA_PHYS_ADJ GENMASK(22, 20)
+#define LPDPTX_CFG_PMA_PHYS_ADJ_OV BIT(19)
+
+#define LPDPTX_AUX_CONTROL 0x4000
+#define LPDPTX_AUX_PWN_DOWN 0x10
+#define LPDPTX_AUX_CLAMP_EN 0x04
+#define LPDPTX_SLEEP_B_BIG_IN 0x02
+#define LPDPTX_SLEEP_B_SML_IN 0x01
+#define LPDPTX_TXTERM_CODEMSB 0x400
+#define LPDPTX_TXTERM_CODE GENMASK(9, 5)
+
+/* pipehandler registers */
+#define PIPEHANDLER_OVERRIDE 0x00
+#define PIPEHANDLER_OVERRIDE_RXVALID BIT(0)
+#define PIPEHANDLER_OVERRIDE_RXDETECT BIT(2)
+
+#define PIPEHANDLER_OVERRIDE_VALUES 0x04
+
+#define PIPEHANDLER_MUX_CTRL 0x0c
+#define PIPEHANDLER_MUX_MODE GENMASK(1, 0)
+#define PIPEHANDLER_MUX_MODE_USB3PHY 0
+#define PIPEHANDLER_MUX_MODE_DUMMY_PHY 2
+#define PIPEHANDLER_CLK_SELECT GENMASK(5, 3)
+#define PIPEHANDLER_CLK_USB3PHY 1
+#define PIPEHANDLER_CLK_DUMMY_PHY 4
+#define PIPEHANDLER_LOCK_REQ 0x10
+#define PIPEHANDLER_LOCK_ACK 0x14
+#define PIPEHANDLER_LOCK_EN BIT(0)
+
+#define PIPEHANDLER_AON_GEN 0x1C
+#define PIPEHANDLER_AON_GEN_DWC3_FORCE_CLAMP_EN BIT(4)
+#define PIPEHANDLER_AON_GEN_DWC3_RESET_N BIT(0)
+
+#define PIPEHANDLER_NONSELECTED_OVERRIDE 0x20
+#define PIPEHANDLER_NONSELECTED_NATIVE_RESET BIT(12)
+#define PIPEHANDLER_DUMMY_PHY_EN BIT(15)
+#define PIPEHANDLER_NONSELECTED_NATIVE_POWER_DOWN GENMASK(3, 0)
+
+/* USB2 PHY regs */
+#define USB2PHY_USBCTL 0x00
+#define USB2PHY_USBCTL_HOST_EN BIT(1)
+
+#define USB2PHY_CTL 0x04
+#define USB2PHY_CTL_RESET BIT(0)
+#define USB2PHY_CTL_PORT_RESET BIT(1)
+#define USB2PHY_CTL_APB_RESET_N BIT(2)
+#define USB2PHY_CTL_SIDDQ BIT(3)
+
+#define USB2PHY_SIG 0x08
+#define USB2PHY_SIG_VBUSDET_FORCE_VAL BIT(0)
+#define USB2PHY_SIG_VBUSDET_FORCE_EN BIT(1)
+#define USB2PHY_SIG_VBUSVLDEXT_FORCE_VAL BIT(2)
+#define USB2PHY_SIG_VBUSVLDEXT_FORCE_EN BIT(3)
+#define USB2PHY_SIG_HOST (7 << 12)
+
+static const struct {
+ const struct atcphy_mode_configuration normal;
+ const struct atcphy_mode_configuration swapped;
+ bool enable_dp_aux;
+ enum atcphy_pipehandler_state pipehandler_state;
+} atcphy_modes[] = {
+ [APPLE_ATCPHY_MODE_OFF] = {
+ .normal = {
+ .crossbar = ACIOPHY_CROSSBAR_PROTOCOL_USB3,
+ .crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_NONE,
+ .crossbar_dp_both_pma = false,
+ .lane_mode = {ACIOPHY_LANE_MODE_OFF, ACIOPHY_LANE_MODE_OFF},
+ .dp_lane = {false, false},
+ .set_swap = false,
+ },
+ .swapped = {
+ .crossbar = ACIOPHY_CROSSBAR_PROTOCOL_USB3_SWAPPED,
+ .crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_NONE,
+ .crossbar_dp_both_pma = false,
+ .lane_mode = {ACIOPHY_LANE_MODE_OFF, ACIOPHY_LANE_MODE_OFF},
+ .dp_lane = {false, false},
+ .set_swap = false, /* doesn't matter since the SS lanes are off */
+ },
+ .enable_dp_aux = false,
+ .pipehandler_state = ATCPHY_PIPEHANDLER_STATE_USB2,
+ },
+ [APPLE_ATCPHY_MODE_USB2] = {
+ .normal = {
+ .crossbar = ACIOPHY_CROSSBAR_PROTOCOL_USB3,
+ .crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_NONE,
+ .crossbar_dp_both_pma = false,
+ .lane_mode = {ACIOPHY_LANE_MODE_OFF, ACIOPHY_LANE_MODE_OFF},
+ .dp_lane = {false, false},
+ .set_swap = false,
+ },
+ .swapped = {
+ .crossbar = ACIOPHY_CROSSBAR_PROTOCOL_USB3_SWAPPED,
+ .crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_NONE,
+ .crossbar_dp_both_pma = false,
+ .lane_mode = {ACIOPHY_LANE_MODE_OFF, ACIOPHY_LANE_MODE_OFF},
+ .dp_lane = {false, false},
+ .set_swap = false, /* doesn't matter since the SS lanes are off */
+ },
+ .enable_dp_aux = false,
+ .pipehandler_state = ATCPHY_PIPEHANDLER_STATE_USB2,
+ },
+ [APPLE_ATCPHY_MODE_USB3] = {
+ .normal = {
+ .crossbar = ACIOPHY_CROSSBAR_PROTOCOL_USB3,
+ .crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_NONE,
+ .crossbar_dp_both_pma = false,
+ .lane_mode = {ACIOPHY_LANE_MODE_USB3, ACIOPHY_LANE_MODE_OFF},
+ .dp_lane = {false, false},
+ .set_swap = false,
+ },
+ .swapped = {
+ .crossbar = ACIOPHY_CROSSBAR_PROTOCOL_USB3_SWAPPED,
+ .crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_NONE,
+ .crossbar_dp_both_pma = false,
+ .lane_mode = {ACIOPHY_LANE_MODE_OFF, ACIOPHY_LANE_MODE_USB3},
+ .dp_lane = {false, false},
+ .set_swap = true,
+ },
+ .enable_dp_aux = false,
+ .pipehandler_state = ATCPHY_PIPEHANDLER_STATE_USB3,
+ },
+ [APPLE_ATCPHY_MODE_USB3_DP] = {
+ .normal = {
+ .crossbar = ACIOPHY_CROSSBAR_PROTOCOL_USB3_DP,
+ .crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_UNK008,
+ .crossbar_dp_both_pma = false,
+ .lane_mode = {ACIOPHY_LANE_MODE_USB3, ACIOPHY_LANE_MODE_DP},
+ .dp_lane = {false, true},
+ .set_swap = false,
+ },
+ .swapped = {
+ .crossbar = ACIOPHY_CROSSBAR_PROTOCOL_USB3_DP_SWAPPED,
+ .crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_UNK008,
+ .crossbar_dp_both_pma = false,
+ .lane_mode = {ACIOPHY_LANE_MODE_DP, ACIOPHY_LANE_MODE_USB3},
+ .dp_lane = {true, false},
+ .set_swap = true,
+ },
+ .enable_dp_aux = true,
+ .pipehandler_state = ATCPHY_PIPEHANDLER_STATE_USB3,
+ },
+ [APPLE_ATCPHY_MODE_USB4] = {
+ .normal = {
+ .crossbar = ACIOPHY_CROSSBAR_PROTOCOL_USB4,
+ .crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_NONE,
+ .crossbar_dp_both_pma = false,
+ .lane_mode = {ACIOPHY_LANE_MODE_USB4, ACIOPHY_LANE_MODE_USB4},
+ .dp_lane = {false, false},
+ .set_swap = false,
+ },
+ .swapped = {
+ .crossbar = ACIOPHY_CROSSBAR_PROTOCOL_USB4_SWAPPED,
+ .crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_NONE,
+ .crossbar_dp_both_pma = false,
+ .lane_mode = {ACIOPHY_LANE_MODE_USB4, ACIOPHY_LANE_MODE_USB4},
+ .dp_lane = {false, false},
+ .set_swap = false, /* intentionally false */
+ },
+ .enable_dp_aux = false,
+ .pipehandler_state = ATCPHY_PIPEHANDLER_STATE_USB2,
+ },
+ [APPLE_ATCPHY_MODE_DP] = {
+ .normal = {
+ .crossbar = ACIOPHY_CROSSBAR_PROTOCOL_DP,
+ .crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_UNK100,
+ .crossbar_dp_both_pma = true,
+ .lane_mode = {ACIOPHY_LANE_MODE_DP, ACIOPHY_LANE_MODE_DP},
+ .dp_lane = {true, true},
+ .set_swap = false,
+ },
+ .swapped = {
+ .crossbar = ACIOPHY_CROSSBAR_PROTOCOL_DP,
+ .crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_UNK008,
+ .crossbar_dp_both_pma = false, /* intentionally false */
+ .lane_mode = {ACIOPHY_LANE_MODE_DP, ACIOPHY_LANE_MODE_DP},
+ .dp_lane = {true, true},
+ .set_swap = false, /* intentionally false */
+ },
+ .enable_dp_aux = true,
+ .pipehandler_state = ATCPHY_PIPEHANDLER_STATE_USB2,
+ },
+};
+
+static const struct atcphy_dp_link_rate_configuration dp_lr_config[] = {
+ [ATCPHY_DP_LINK_RATE_RBR] = {
+ .freqinit_count_target = 0x21c,
+ .fbdivn_frac_den = 0x0,
+ .fbdivn_frac_num = 0x0,
+ .pclk_div_sel = 0x13,
+ .lfclk_ctrl = 0x5,
+ .vclk_op_divn = 0x2,
+ .plla_clkout_vreg_bypass = true,
+ .bypass_txa_ldoclk = true,
+ .txa_div2_en = true,
+ },
+ [ATCPHY_DP_LINK_RATE_HBR] = {
+ .freqinit_count_target = 0x1c2,
+ .fbdivn_frac_den = 0x3ffe,
+ .fbdivn_frac_num = 0x1fff,
+ .pclk_div_sel = 0x9,
+ .lfclk_ctrl = 0x5,
+ .vclk_op_divn = 0x2,
+ .plla_clkout_vreg_bypass = true,
+ .bypass_txa_ldoclk = true,
+ .txa_div2_en = false,
+ },
+ [ATCPHY_DP_LINK_RATE_HBR2] = {
+ .freqinit_count_target = 0x1c2,
+ .fbdivn_frac_den = 0x3ffe,
+ .fbdivn_frac_num = 0x1fff,
+ .pclk_div_sel = 0x4,
+ .lfclk_ctrl = 0x5,
+ .vclk_op_divn = 0x0,
+ .plla_clkout_vreg_bypass = true,
+ .bypass_txa_ldoclk = true,
+ .txa_div2_en = false,
+ },
+ [ATCPHY_DP_LINK_RATE_HBR3] = {
+ .freqinit_count_target = 0x2a3,
+ .fbdivn_frac_den = 0x3ffc,
+ .fbdivn_frac_num = 0x2ffd,
+ .pclk_div_sel = 0x4,
+ .lfclk_ctrl = 0x6,
+ .vclk_op_divn = 0x0,
+ .plla_clkout_vreg_bypass = false,
+ .bypass_txa_ldoclk = false,
+ .txa_div2_en = false,
+ },
+};
+
+static inline void mask32(void __iomem *reg, u32 mask, u32 set)
+{
+ u32 value = readl(reg);
+ value &= ~mask;
+ value |= set;
+ writel(value, reg);
+}
+
+static inline void core_mask32(struct apple_atcphy *atcphy, u32 reg, u32 mask,
+ u32 set)
+{
+ mask32(atcphy->regs.core + reg, mask, set);
+}
+
+static inline void set32(void __iomem *reg, u32 set)
+{
+ mask32(reg, 0, set);
+}
+
+static inline void core_set32(struct apple_atcphy *atcphy, u32 reg, u32 set)
+{
+ core_mask32(atcphy, reg, 0, set);
+}
+
+static inline void clear32(void __iomem *reg, u32 clear)
+{
+ mask32(reg, clear, 0);
+}
+
+static inline void core_clear32(struct apple_atcphy *atcphy, u32 reg, u32 clear)
+{
+ core_mask32(atcphy, reg, clear, 0);
+}
+
+static void atcphy_apply_tunable(struct apple_atcphy *atcphy,
+ void __iomem *regs,
+ struct atcphy_tunable *tunable)
+{
+ size_t i;
+
+ for (i = 0; i < tunable->sz; ++i)
+ mask32(regs + tunable->values[i].offset,
+ tunable->values[i].mask, tunable->values[i].value);
+}
+
+static void atcphy_apply_tunables(struct apple_atcphy *atcphy,
+ enum atcphy_mode mode)
+{
+ int lane0 = atcphy->swap_lanes ? 1 : 0;
+ int lane1 = atcphy->swap_lanes ? 0 : 1;
+
+ atcphy_apply_tunable(atcphy, atcphy->regs.axi2af,
+ &atcphy->tunables.axi2af);
+ atcphy_apply_tunable(atcphy, atcphy->regs.core,
+ &atcphy->tunables.common);
+
+ switch (mode) {
+ case APPLE_ATCPHY_MODE_USB3:
+ atcphy_apply_tunable(atcphy, atcphy->regs.core,
+ &atcphy->tunables.lane_usb3[lane0]);
+ atcphy_apply_tunable(atcphy, atcphy->regs.core,
+ &atcphy->tunables.lane_usb3[lane1]);
+ break;
+
+ case APPLE_ATCPHY_MODE_USB3_DP:
+ atcphy_apply_tunable(atcphy, atcphy->regs.core,
+ &atcphy->tunables.lane_usb3[lane0]);
+ atcphy_apply_tunable(atcphy, atcphy->regs.core,
+ &atcphy->tunables.lane_displayport[lane1]);
+ break;
+
+ case APPLE_ATCPHY_MODE_DP:
+ atcphy_apply_tunable(atcphy, atcphy->regs.core,
+ &atcphy->tunables.lane_displayport[lane0]);
+ atcphy_apply_tunable(atcphy, atcphy->regs.core,
+ &atcphy->tunables.lane_displayport[lane1]);
+ break;
+
+ case APPLE_ATCPHY_MODE_USB4:
+ atcphy_apply_tunable(atcphy, atcphy->regs.core,
+ &atcphy->tunables.lane_usb4[lane0]);
+ atcphy_apply_tunable(atcphy, atcphy->regs.core,
+ &atcphy->tunables.lane_usb4[lane1]);
+ break;
+
+ default:
+ dev_warn(atcphy->dev,
+ "Unknown mode %d in atcphy_apply_tunables\n", mode);
+ fallthrough;
+ case APPLE_ATCPHY_MODE_OFF:
+ case APPLE_ATCPHY_MODE_USB2:
+ break;
+ }
+}
+
+static void atcphy_setup_pll_fuses(struct apple_atcphy *atcphy)
+{
+ void __iomem *regs = atcphy->regs.core;
+
+ if (!atcphy->fuses.present)
+ return;
+
+ /* CIO3PLL fuses */
+ mask32(regs + CIO3PLL_DCO_NCTRL, CIO3PLL_DCO_COARSEBIN_EFUSE0,
+ FIELD_PREP(CIO3PLL_DCO_COARSEBIN_EFUSE0,
+ atcphy->fuses.cio3pll_dco_coarsebin[0]));
+ mask32(regs + CIO3PLL_DCO_NCTRL, CIO3PLL_DCO_COARSEBIN_EFUSE1,
+ FIELD_PREP(CIO3PLL_DCO_COARSEBIN_EFUSE1,
+ atcphy->fuses.cio3pll_dco_coarsebin[1]));
+ mask32(regs + CIO3PLL_FRACN_CAN, CIO3PLL_DLL_CAL_START_CAPCODE,
+ FIELD_PREP(CIO3PLL_DLL_CAL_START_CAPCODE,
+ atcphy->fuses.cio3pll_dll_start_capcode[0]));
+
+ if (atcphy->quirks.t8103_cio3pll_workaround) {
+ mask32(regs + AUS_COMMON_SHIM_BLK_VREG, AUS_VREG_TRIM,
+ FIELD_PREP(AUS_VREG_TRIM,
+ atcphy->fuses.aus_cmn_shm_vreg_trim));
+ mask32(regs + CIO3PLL_FRACN_CAN, CIO3PLL_DLL_CAL_START_CAPCODE,
+ FIELD_PREP(CIO3PLL_DLL_CAL_START_CAPCODE,
+ atcphy->fuses.cio3pll_dll_start_capcode[1]));
+ mask32(regs + CIO3PLL_DTC_VREG, CIO3PLL_DTC_VREG_ADJUST,
+ FIELD_PREP(CIO3PLL_DTC_VREG_ADJUST,
+ atcphy->fuses.cio3pll_dtc_vreg_adjust));
+ } else {
+ mask32(regs + CIO3PLL_DTC_VREG, CIO3PLL_DTC_VREG_ADJUST,
+ FIELD_PREP(CIO3PLL_DTC_VREG_ADJUST,
+ atcphy->fuses.cio3pll_dtc_vreg_adjust));
+ mask32(regs + AUS_COMMON_SHIM_BLK_VREG, AUS_VREG_TRIM,
+ FIELD_PREP(AUS_VREG_TRIM,
+ atcphy->fuses.aus_cmn_shm_vreg_trim));
+ }
+
+ /* AUSPLL fuses */
+ mask32(regs + AUSPLL_DCO_EFUSE_SPARE, AUSPLL_RODCO_ENCAP_EFUSE,
+ FIELD_PREP(AUSPLL_RODCO_ENCAP_EFUSE,
+ atcphy->fuses.auspll_rodco_encap));
+ mask32(regs + AUSPLL_DCO_EFUSE_SPARE, AUSPLL_RODCO_BIAS_ADJUST_EFUSE,
+ FIELD_PREP(AUSPLL_RODCO_BIAS_ADJUST_EFUSE,
+ atcphy->fuses.auspll_rodco_bias_adjust));
+ mask32(regs + AUSPLL_FRACN_CAN, AUSPLL_DLL_START_CAPCODE,
+ FIELD_PREP(AUSPLL_DLL_START_CAPCODE,
+ atcphy->fuses.auspll_fracn_dll_start_capcode));
+ mask32(regs + AUSPLL_CLKOUT_DTC_VREG, AUSPLL_DTC_VREG_ADJUST,
+ FIELD_PREP(AUSPLL_DTC_VREG_ADJUST,
+ atcphy->fuses.auspll_dtc_vreg_adjust));
+
+ /* TODO: is this actually required again? */
+ mask32(regs + AUS_COMMON_SHIM_BLK_VREG, AUS_VREG_TRIM,
+ FIELD_PREP(AUS_VREG_TRIM, atcphy->fuses.aus_cmn_shm_vreg_trim));
+}
+
+static int atcphy_cio_power_off(struct apple_atcphy *atcphy)
+{
+ u32 reg;
+ int ret;
+
+ /* enable all reset lines */
+ core_clear32(atcphy, ATCPHY_POWER_CTRL, ATCPHY_POWER_PHY_RESET_N);
+ core_clear32(atcphy, ATCPHY_POWER_CTRL, ATCPHY_POWER_APB_RESET_N);
+ core_set32(atcphy, ATCPHY_POWER_CTRL, ATCPHY_POWER_CLAMP_EN);
+ core_clear32(atcphy, ATCPHY_MISC, ATCPHY_MISC_RESET_N);
+
+ // TODO: why clear? is this SLEEP_N? or do we enable some power management here?
+ core_clear32(atcphy, ATCPHY_POWER_CTRL, ATCPHY_POWER_SLEEP_BIG);
+ ret = readl_poll_timeout(atcphy->regs.core + ATCPHY_POWER_STAT, reg,
+ !(reg & ATCPHY_POWER_SLEEP_BIG), 100, 100000);
+ if (ret) {
+ dev_err(atcphy->dev, "failed to sleep atcphy \"big\"\n");
+ return ret;
+ }
+
+ core_clear32(atcphy, ATCPHY_POWER_CTRL, ATCPHY_POWER_SLEEP_SMALL);
+ ret = readl_poll_timeout(atcphy->regs.core + ATCPHY_POWER_STAT, reg,
+ !(reg & ATCPHY_POWER_SLEEP_SMALL), 100,
+ 100000);
+ if (ret) {
+ dev_err(atcphy->dev, "failed to sleep atcphy \"small\"\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int atcphy_cio_power_on(struct apple_atcphy *atcphy)
+{
+ u32 reg;
+ int ret;
+
+ core_set32(atcphy, ATCPHY_MISC, ATCPHY_MISC_RESET_N);
+
+ // TODO: why set?! see above
+ core_set32(atcphy, ATCPHY_POWER_CTRL, ATCPHY_POWER_SLEEP_SMALL);
+ ret = readl_poll_timeout(atcphy->regs.core + ATCPHY_POWER_STAT, reg,
+ reg & ATCPHY_POWER_SLEEP_SMALL, 100, 100000);
+ if (ret) {
+ dev_err(atcphy->dev, "failed to wakeup atcphy \"small\"\n");
+ return ret;
+ }
+
+ core_set32(atcphy, ATCPHY_POWER_CTRL, ATCPHY_POWER_SLEEP_BIG);
+ ret = readl_poll_timeout(atcphy->regs.core + ATCPHY_POWER_STAT, reg,
+ reg & ATCPHY_POWER_SLEEP_BIG, 100, 100000);
+ if (ret) {
+ dev_err(atcphy->dev, "failed to wakeup atcphy \"big\"\n");
+ return ret;
+ }
+
+ core_clear32(atcphy, ATCPHY_POWER_CTRL, ATCPHY_POWER_CLAMP_EN);
+ core_set32(atcphy, ATCPHY_POWER_CTRL, ATCPHY_POWER_APB_RESET_N);
+
+ return 0;
+}
+
+static void atcphy_configure_lanes(struct apple_atcphy *atcphy,
+ enum atcphy_mode mode)
+{
+ const struct atcphy_mode_configuration *mode_cfg;
+
+ if (atcphy->swap_lanes)
+ mode_cfg = &atcphy_modes[mode].swapped;
+ else
+ mode_cfg = &atcphy_modes[mode].normal;
+
+ trace_atcphy_configure_lanes(mode, mode_cfg);
+
+ if (mode_cfg->set_swap)
+ core_set32(atcphy, ATCPHY_MISC, ATCPHY_MISC_LANE_SWAP);
+ else
+ core_clear32(atcphy, ATCPHY_MISC, ATCPHY_MISC_LANE_SWAP);
+
+ if (mode_cfg->dp_lane[0]) {
+ core_set32(atcphy, LN0_AUSPMA_RX_TOP + LN_AUSPMA_RX_TOP_PMAFSM,
+ LN_AUSPMA_RX_TOP_PMAFSM_PCS_OV);
+ core_clear32(atcphy,
+ LN0_AUSPMA_RX_TOP + LN_AUSPMA_RX_TOP_PMAFSM,
+ LN_AUSPMA_RX_TOP_PMAFSM_PCS_REQ);
+ }
+ if (mode_cfg->dp_lane[1]) {
+ core_set32(atcphy, LN1_AUSPMA_RX_TOP + LN_AUSPMA_RX_TOP_PMAFSM,
+ LN_AUSPMA_RX_TOP_PMAFSM_PCS_OV);
+ core_clear32(atcphy,
+ LN1_AUSPMA_RX_TOP + LN_AUSPMA_RX_TOP_PMAFSM,
+ LN_AUSPMA_RX_TOP_PMAFSM_PCS_REQ);
+ }
+
+ core_mask32(atcphy, ACIOPHY_LANE_MODE, ACIOPHY_LANE_MODE_RX0,
+ FIELD_PREP(ACIOPHY_LANE_MODE_RX0, mode_cfg->lane_mode[0]));
+ core_mask32(atcphy, ACIOPHY_LANE_MODE, ACIOPHY_LANE_MODE_TX0,
+ FIELD_PREP(ACIOPHY_LANE_MODE_TX0, mode_cfg->lane_mode[0]));
+ core_mask32(atcphy, ACIOPHY_LANE_MODE, ACIOPHY_LANE_MODE_RX1,
+ FIELD_PREP(ACIOPHY_LANE_MODE_RX1, mode_cfg->lane_mode[1]));
+ core_mask32(atcphy, ACIOPHY_LANE_MODE, ACIOPHY_LANE_MODE_TX1,
+ FIELD_PREP(ACIOPHY_LANE_MODE_TX1, mode_cfg->lane_mode[1]));
+ core_mask32(atcphy, ACIOPHY_CROSSBAR, ACIOPHY_CROSSBAR_PROTOCOL,
+ FIELD_PREP(ACIOPHY_CROSSBAR_PROTOCOL, mode_cfg->crossbar));
+
+ core_mask32(atcphy, ACIOPHY_CROSSBAR, ACIOPHY_CROSSBAR_DP_SINGLE_PMA,
+ FIELD_PREP(ACIOPHY_CROSSBAR_DP_SINGLE_PMA,
+ mode_cfg->crossbar_dp_single_pma));
+ if (mode_cfg->crossbar_dp_both_pma)
+ core_set32(atcphy, ACIOPHY_CROSSBAR,
+ ACIOPHY_CROSSBAR_DP_BOTH_PMA);
+ else
+ core_clear32(atcphy, ACIOPHY_CROSSBAR,
+ ACIOPHY_CROSSBAR_DP_BOTH_PMA);
+}
+
+static int atcphy_pipehandler_lock(struct apple_atcphy *atcphy)
+{
+ int ret;
+ u32 reg;
+
+ if (readl_relaxed(atcphy->regs.pipehandler + PIPEHANDLER_LOCK_REQ) &
+ PIPEHANDLER_LOCK_EN)
+ dev_warn(atcphy->dev, "pipehandler already locked\n");
+
+ set32(atcphy->regs.pipehandler + PIPEHANDLER_LOCK_REQ,
+ PIPEHANDLER_LOCK_EN);
+
+ ret = readl_poll_timeout(atcphy->regs.pipehandler +
+ PIPEHANDLER_LOCK_ACK,
+ reg, reg & PIPEHANDLER_LOCK_EN, 1000, 1000000);
+ if (ret) {
+ clear32(atcphy->regs.pipehandler + PIPEHANDLER_LOCK_REQ, 1);
+ dev_err(atcphy->dev,
+ "pipehandler lock not acked, this type-c port is probably dead until the next reboot.\n");
+ }
+
+ return ret;
+}
+
+static int atcphy_pipehandler_unlock(struct apple_atcphy *atcphy)
+{
+ int ret;
+ u32 reg;
+
+ clear32(atcphy->regs.pipehandler + PIPEHANDLER_LOCK_REQ,
+ PIPEHANDLER_LOCK_EN);
+ ret = readl_poll_timeout(
+ atcphy->regs.pipehandler + PIPEHANDLER_LOCK_ACK, reg,
+ !(reg & PIPEHANDLER_LOCK_EN), 1000, 1000000);
+ if (ret)
+ dev_err(atcphy->dev,
+ "pipehandler lock release not acked, this type-c port is probably dead until the next reboot.\n");
+
+ return ret;
+}
+
+static int atcphy_configure_pipehandler(struct apple_atcphy *atcphy,
+ enum atcphy_pipehandler_state state)
+{
+ int ret;
+ u32 reg;
+
+ if (atcphy->pipehandler_state == state)
+ return 0;
+
+ clear32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE_VALUES,
+ 14); // TODO: why 14?
+ set32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE,
+ PIPEHANDLER_OVERRIDE_RXVALID | PIPEHANDLER_OVERRIDE_RXDETECT);
+
+ ret = atcphy_pipehandler_lock(atcphy);
+ if (ret)
+ return ret;
+
+ switch (state) {
+ case ATCPHY_PIPEHANDLER_STATE_USB3:
+ core_set32(atcphy, ACIOPHY_TOP_BIST_PHY_CFG0,
+ ACIOPHY_TOP_BIST_PHY_CFG0_LN0_RESET_N);
+ core_set32(atcphy, ACIOPHY_TOP_BIST_OV_CFG,
+ ACIOPHY_TOP_BIST_OV_CFG_LN0_RESET_N_OV);
+ ret = readl_poll_timeout(
+ atcphy->regs.core + ACIOPHY_TOP_PHY_STAT, reg,
+ !(reg & ACIOPHY_TOP_PHY_STAT_LN0_UNK23), 100, 100000);
+ if (ret)
+ dev_warn(
+ atcphy->dev,
+ "timed out waiting for ACIOPHY_TOP_PHY_STAT_LN0_UNK23\n");
+
+ // TODO: macOS does this but this breaks waiting for
+ // ACIOPHY_TOP_PHY_STAT_LN0_UNK0 then for some reason :/
+ // this is probably status reset which clears the ln0
+ // ready status but then the ready status never comes
+ // up again
+#if 0
+ core_set32(atcphy, ACIOPHY_TOP_BIST_READ_CTRL,
+ ACIOPHY_TOP_BIST_READ_CTRL_LN0_PHY_STATUS_RE);
+ core_clear32(atcphy, ACIOPHY_TOP_BIST_READ_CTRL,
+ ACIOPHY_TOP_BIST_READ_CTRL_LN0_PHY_STATUS_RE);
+#endif
+ core_mask32(atcphy, ACIOPHY_TOP_BIST_PHY_CFG1,
+ ACIOPHY_TOP_BIST_PHY_CFG1_LN0_PWR_DOWN,
+ FIELD_PREP(ACIOPHY_TOP_BIST_PHY_CFG1_LN0_PWR_DOWN,
+ 3));
+ core_set32(atcphy, ACIOPHY_TOP_BIST_OV_CFG,
+ ACIOPHY_TOP_BIST_OV_CFG_LN0_PWR_DOWN_OV);
+ core_set32(atcphy, ACIOPHY_TOP_BIST_CIOPHY_CFG1,
+ ACIOPHY_TOP_BIST_CIOPHY_CFG1_CLK_EN);
+ core_set32(atcphy, ACIOPHY_TOP_BIST_CIOPHY_CFG1,
+ ACIOPHY_TOP_BIST_CIOPHY_CFG1_BIST_EN);
+ writel(0, atcphy->regs.core + ACIOPHY_TOP_BIST_CIOPHY_CFG1);
+
+ ret = readl_poll_timeout(
+ atcphy->regs.core + ACIOPHY_TOP_PHY_STAT, reg,
+ (reg & ACIOPHY_TOP_PHY_STAT_LN0_UNK0), 100, 100000);
+ if (ret)
+ dev_warn(
+ atcphy->dev,
+ "timed out waiting for ACIOPHY_TOP_PHY_STAT_LN0_UNK0\n");
+
+ ret = readl_poll_timeout(
+ atcphy->regs.core + ACIOPHY_TOP_PHY_STAT, reg,
+ !(reg & ACIOPHY_TOP_PHY_STAT_LN0_UNK23), 100, 100000);
+ if (ret)
+ dev_warn(
+ atcphy->dev,
+ "timed out waiting for ACIOPHY_TOP_PHY_STAT_LN0_UNK23\n");
+
+ writel(0, atcphy->regs.core + ACIOPHY_TOP_BIST_OV_CFG);
+ core_set32(atcphy, ACIOPHY_TOP_BIST_CIOPHY_CFG1,
+ ACIOPHY_TOP_BIST_CIOPHY_CFG1_CLK_EN);
+ core_set32(atcphy, ACIOPHY_TOP_BIST_CIOPHY_CFG1,
+ ACIOPHY_TOP_BIST_CIOPHY_CFG1_BIST_EN);
+
+ /* switch dwc3's superspeed PHY to the real physical PHY */
+ clear32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL,
+ PIPEHANDLER_CLK_SELECT);
+ clear32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL,
+ PIPEHANDLER_MUX_MODE);
+ mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL,
+ PIPEHANDLER_CLK_SELECT,
+ FIELD_PREP(PIPEHANDLER_CLK_SELECT,
+ PIPEHANDLER_CLK_USB3PHY));
+ mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL,
+ PIPEHANDLER_MUX_MODE,
+ FIELD_PREP(PIPEHANDLER_MUX_MODE,
+ PIPEHANDLER_MUX_MODE_USB3PHY));
+
+ /* use real rx detect/valid values again */
+ clear32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE,
+ PIPEHANDLER_OVERRIDE_RXVALID |
+ PIPEHANDLER_OVERRIDE_RXDETECT);
+ break;
+ default:
+ dev_warn(
+ atcphy->dev,
+ "unknown mode in pipehandler_configure: %d, switching to safe state\n",
+ state);
+ fallthrough;
+ case ATCPHY_PIPEHANDLER_STATE_USB2:
+ /* switch dwc3's superspeed PHY back to the dummy (and also USB4 PHY?) */
+ clear32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL,
+ PIPEHANDLER_CLK_SELECT);
+ clear32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL,
+ PIPEHANDLER_MUX_MODE);
+ mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL,
+ PIPEHANDLER_CLK_SELECT,
+ FIELD_PREP(PIPEHANDLER_CLK_SELECT,
+ PIPEHANDLER_CLK_DUMMY_PHY));
+ mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL,
+ PIPEHANDLER_MUX_MODE,
+ FIELD_PREP(PIPEHANDLER_MUX_MODE,
+ PIPEHANDLER_MUX_MODE_DUMMY_PHY));
+
+ /* keep ignoring rx detect and valid values from the USB3/4 PHY? */
+ set32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE,
+ PIPEHANDLER_OVERRIDE_RXVALID |
+ PIPEHANDLER_OVERRIDE_RXDETECT);
+ break;
+ }
+
+ ret = atcphy_pipehandler_unlock(atcphy);
+ if (ret)
+ return ret;
+
+ // TODO: macos seems to always clear it for USB3 - what about USB2/4?
+ clear32(atcphy->regs.pipehandler + PIPEHANDLER_NONSELECTED_OVERRIDE,
+ PIPEHANDLER_NONSELECTED_NATIVE_RESET);
+
+ // TODO: why? without this superspeed devices sometimes come up as highspeed
+ msleep(500);
+
+ atcphy->pipehandler_state = state;
+
+ return 0;
+}
+
+static void atcphy_enable_dp_aux(struct apple_atcphy *atcphy)
+{
+ core_set32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0,
+ DPTXPHY_PMA_LANE_RESET_N);
+ core_set32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0,
+ DPTXPHY_PMA_LANE_RESET_N_OV);
+
+ core_mask32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0,
+ DPRX_PCLK_SELECT, FIELD_PREP(DPRX_PCLK_SELECT, 1));
+ core_set32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0,
+ DPRX_PCLK_ENABLE);
+
+ core_mask32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0,
+ DPTX_PCLK1_SELECT, FIELD_PREP(DPTX_PCLK1_SELECT, 1));
+ core_set32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0,
+ DPTX_PCLK1_ENABLE);
+
+ core_mask32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0,
+ DPTX_PCLK2_SELECT, FIELD_PREP(DPTX_PCLK2_SELECT, 1));
+ core_set32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0,
+ DPTX_PCLK2_ENABLE);
+
+ core_set32(atcphy, ACIOPHY_PLL_COMMON_CTRL,
+ ACIOPHY_PLL_WAIT_FOR_CMN_READY_BEFORE_RESET_EXIT);
+
+ set32(atcphy->regs.lpdptx + LPDPTX_AUX_CONTROL, LPDPTX_AUX_CLAMP_EN);
+ set32(atcphy->regs.lpdptx + LPDPTX_AUX_CONTROL, LPDPTX_SLEEP_B_SML_IN);
+ udelay(2);
+ set32(atcphy->regs.lpdptx + LPDPTX_AUX_CONTROL, LPDPTX_SLEEP_B_BIG_IN);
+ udelay(2);
+ clear32(atcphy->regs.lpdptx + LPDPTX_AUX_CONTROL, LPDPTX_AUX_CLAMP_EN);
+ clear32(atcphy->regs.lpdptx + LPDPTX_AUX_CONTROL, LPDPTX_AUX_PWN_DOWN);
+ clear32(atcphy->regs.lpdptx + LPDPTX_AUX_CONTROL,
+ LPDPTX_TXTERM_CODEMSB);
+ mask32(atcphy->regs.lpdptx + LPDPTX_AUX_CONTROL, LPDPTX_TXTERM_CODE,
+ FIELD_PREP(LPDPTX_TXTERM_CODE, 0x16));
+
+ set32(atcphy->regs.lpdptx + LPDPTX_AUX_CFG_BLK_AUX_LDO_CTRL, 0x1c00);
+ mask32(atcphy->regs.lpdptx + LPDPTX_AUX_SHM_CFG_BLK_AUX_CTRL_REG1,
+ LPDPTX_CFG_PMA_PHYS_ADJ, FIELD_PREP(LPDPTX_CFG_PMA_PHYS_ADJ, 5));
+ set32(atcphy->regs.lpdptx + LPDPTX_AUX_SHM_CFG_BLK_AUX_CTRL_REG1,
+ LPDPTX_CFG_PMA_PHYS_ADJ_OV);
+
+ clear32(atcphy->regs.lpdptx + LPDPTX_AUX_CFG_BLK_AUX_MARGIN,
+ LPDPTX_MARGIN_RCAL_RXOFFSET_EN);
+
+ clear32(atcphy->regs.lpdptx + LPDPTX_AUX_CFG_BLK_AUX_CTRL,
+ LPDPTX_BLK_AUX_CTRL_PWRDN);
+ set32(atcphy->regs.lpdptx + LPDPTX_AUX_SHM_CFG_BLK_AUX_CTRL_REG0,
+ LPDPTX_CFG_PMA_AUX_SEL_LF_DATA);
+ mask32(atcphy->regs.lpdptx + LPDPTX_AUX_CFG_BLK_AUX_CTRL,
+ LPDPTX_BLK_AUX_RXOFFSET, FIELD_PREP(LPDPTX_BLK_AUX_RXOFFSET, 3));
+
+ mask32(atcphy->regs.lpdptx + LPDPTX_AUX_CFG_BLK_AUX_MARGIN,
+ LPDPTX_AUX_MARGIN_RCAL_TXSWING,
+ FIELD_PREP(LPDPTX_AUX_MARGIN_RCAL_TXSWING, 12));
+
+ atcphy->dp_link_rate = -1;
+}
+
+static void atcphy_disable_dp_aux(struct apple_atcphy *atcphy)
+{
+ set32(atcphy->regs.lpdptx + LPDPTX_AUX_CONTROL, LPDPTX_AUX_PWN_DOWN);
+ set32(atcphy->regs.lpdptx + LPDPTX_AUX_CFG_BLK_AUX_CTRL,
+ LPDPTX_BLK_AUX_CTRL_PWRDN);
+ set32(atcphy->regs.lpdptx + LPDPTX_AUX_CONTROL, LPDPTX_AUX_CLAMP_EN);
+ clear32(atcphy->regs.lpdptx + LPDPTX_AUX_CONTROL,
+ LPDPTX_SLEEP_B_SML_IN);
+ udelay(2);
+ clear32(atcphy->regs.lpdptx + LPDPTX_AUX_CONTROL,
+ LPDPTX_SLEEP_B_BIG_IN);
+ udelay(2);
+
+ // TODO: maybe?
+ core_clear32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0,
+ DPTXPHY_PMA_LANE_RESET_N);
+ // _OV?
+ core_clear32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0,
+ DPRX_PCLK_ENABLE);
+ core_clear32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0,
+ DPTX_PCLK1_ENABLE);
+ core_clear32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0,
+ DPTX_PCLK2_ENABLE);
+
+ // clear 0x1000000 / BIT(24) maybe
+ // writel(0x1830630, atcphy->regs.core + 0x1028);
+}
+
+static int
+atcphy_dp_configure_lane(struct apple_atcphy *atcphy, unsigned int lane,
+ const struct atcphy_dp_link_rate_configuration *cfg)
+{
+ void __iomem *tx_shm, *rx_shm, *rx_top;
+
+ switch (lane) {
+ case 0:
+ tx_shm = atcphy->regs.core + LN0_AUSPMA_TX_SHM;
+ rx_shm = atcphy->regs.core + LN0_AUSPMA_RX_SHM;
+ rx_top = atcphy->regs.core + LN0_AUSPMA_RX_TOP;
+ break;
+ case 1:
+ tx_shm = atcphy->regs.core + LN1_AUSPMA_TX_SHM;
+ rx_shm = atcphy->regs.core + LN1_AUSPMA_RX_SHM;
+ rx_top = atcphy->regs.core + LN1_AUSPMA_RX_TOP;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_LDOCLK, LN_LDOCLK_EN_SML);
+ set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_LDOCLK, LN_LDOCLK_EN_SML_OV);
+ udelay(2);
+
+ set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_LDOCLK, LN_LDOCLK_EN_BIG);
+ set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_LDOCLK, LN_LDOCLK_EN_BIG_OV);
+ udelay(2);
+
+ if (cfg->bypass_txa_ldoclk) {
+ set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_LDOCLK,
+ LN_LDOCLK_BYPASS_SML);
+ set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_LDOCLK,
+ LN_LDOCLK_BYPASS_SML_OV);
+ udelay(2);
+
+ set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_LDOCLK,
+ LN_LDOCLK_BYPASS_BIG);
+ set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_LDOCLK,
+ LN_LDOCLK_BYPASS_BIG_OV);
+ udelay(2);
+ } else {
+ clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_LDOCLK,
+ LN_LDOCLK_BYPASS_SML);
+ clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_LDOCLK,
+ LN_LDOCLK_BYPASS_SML_OV);
+ udelay(2);
+
+ clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_LDOCLK,
+ LN_LDOCLK_BYPASS_BIG);
+ clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_LDOCLK,
+ LN_LDOCLK_BYPASS_BIG_OV);
+ udelay(2);
+ }
+
+ set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG0,
+ LN_BYTECLK_RESET_SYNC_SEL_OV);
+ set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG0,
+ LN_BYTECLK_RESET_SYNC_EN);
+ set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG0,
+ LN_BYTECLK_RESET_SYNC_EN_OV);
+ clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG0,
+ LN_BYTECLK_RESET_SYNC_CLR);
+ set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG0,
+ LN_BYTECLK_RESET_SYNC_CLR_OV);
+
+ if (cfg->txa_div2_en)
+ set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG1,
+ LN_TXA_DIV2_EN);
+ else
+ clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG1,
+ LN_TXA_DIV2_EN);
+ set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG1, LN_TXA_DIV2_EN_OV);
+ set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG1, LN_TXA_CLK_EN);
+ set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG1, LN_TXA_CLK_EN_OV);
+ clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG1, LN_TXA_DIV2_RESET);
+ set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG1,
+ LN_TXA_DIV2_RESET_OV);
+
+ mask32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG0, LN_TXA_CAL_CTRL_BASE,
+ FIELD_PREP(LN_TXA_CAL_CTRL_BASE, 0xf));
+ set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG0, LN_TXA_CAL_CTRL_BASE_OV);
+ mask32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG0, LN_TXA_CAL_CTRL,
+ FIELD_PREP(LN_TXA_CAL_CTRL, 0x3f)); // TODO: 3f?
+ set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG0, LN_TXA_CAL_CTRL_OV);
+
+ clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG2, LN_TXA_MARGIN);
+ set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG2, LN_TXA_MARGIN_OV);
+ clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG2, LN_TXA_MARGIN_2R);
+ set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG2, LN_TXA_MARGIN_2R_OV);
+
+ clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG3, LN_TXA_MARGIN_POST);
+ set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG3, LN_TXA_MARGIN_POST_OV);
+ clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG3, LN_TXA_MARGIN_POST_2R);
+ set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG3, LN_TXA_MARGIN_POST_2R_OV);
+ clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG3, LN_TXA_MARGIN_POST_4R);
+ set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG3, LN_TXA_MARGIN_POST_4R_OV);
+ clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG3, LN_TXA_MARGIN_PRE);
+ set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG3, LN_TXA_MARGIN_PRE_OV);
+ clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG3, LN_TXA_MARGIN_PRE_2R);
+ set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG3, LN_TXA_MARGIN_PRE_2R_OV);
+ clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG3, LN_TXA_MARGIN_PRE_4R);
+ set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG3, LN_TXA_MARGIN_PRE_4R_OV);
+
+ clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG0, LN_TXA_HIZ);
+ set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG0, LN_TXA_HIZ_OV);
+
+ clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_AFE_CTRL1,
+ LN_RX_DIV20_RESET_N);
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_AFE_CTRL1,
+ LN_RX_DIV20_RESET_N_OV);
+ udelay(2);
+
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_AFE_CTRL1, LN_RX_DIV20_RESET_N);
+
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL12,
+ LN_TX_BYTECLK_RESET_SYNC_EN);
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL12,
+ LN_TX_BYTECLK_RESET_SYNC_EN_OV);
+
+ mask32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_SAVOS_CTRL16, LN_TX_CAL_CODE,
+ FIELD_PREP(LN_TX_CAL_CODE, 6)); // TODO 6?
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_SAVOS_CTRL16, LN_TX_CAL_CODE_OV);
+
+ mask32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19,
+ LN_TX_CLK_DLY_CTRL_TAPGEN,
+ FIELD_PREP(LN_TX_CLK_DLY_CTRL_TAPGEN, 3));
+
+ clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL10, LN_DTVREG_ADJUST);
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL13, LN_DTVREG_ADJUST_OV);
+
+ clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_SAVOS_CTRL16, LN_RXTERM_EN);
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_SAVOS_CTRL16, LN_RXTERM_EN_OV);
+
+ clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19, LN_TX_TEST_EN);
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19, LN_TX_TEST_EN_OV);
+
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22,
+ LN_VREF_TEST_RXLPBKDT_EN);
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22,
+ LN_VREF_TEST_RXLPBKDT_EN_OV);
+ mask32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22,
+ LN_VREF_LPBKIN_DATA, FIELD_PREP(LN_VREF_LPBKIN_DATA, 3));
+ mask32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22, LN_VREF_BIAS_SEL,
+ FIELD_PREP(LN_VREF_BIAS_SEL, 2));
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22,
+ LN_VREF_BIAS_SEL_OV);
+ mask32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22,
+ LN_VREF_ADJUST_GRAY, FIELD_PREP(LN_VREF_ADJUST_GRAY, 0x18));
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22,
+ LN_VREF_ADJUST_GRAY_OV);
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22, LN_VREF_EN);
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22, LN_VREF_EN_OV);
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22, LN_VREF_BOOST_EN);
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22,
+ LN_VREF_BOOST_EN_OV);
+ udelay(2);
+
+ clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22, LN_VREF_BOOST_EN);
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22,
+ LN_VREF_BOOST_EN_OV);
+ udelay(2);
+
+ clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL13, LN_TX_PRE_EN);
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL13, LN_TX_PRE_EN_OV);
+ clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL13, LN_TX_PST1_EN);
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL13, LN_TX_PST1_EN_OV);
+
+ clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL12, LN_TX_PBIAS_EN);
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL12, LN_TX_PBIAS_EN_OV);
+
+ clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_SAVOS_CTRL16,
+ LN_RXTERM_PULLUP_LEAK_EN);
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_SAVOS_CTRL16,
+ LN_RXTERM_PULLUP_LEAK_EN_OV);
+
+ set32(rx_top + LN_AUSPMA_RX_TOP_TJ_CFG_RX_TXMODE, LN_RX_TXMODE);
+
+ if (cfg->txa_div2_en)
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19,
+ LN_TX_CLK_DIV2_EN);
+ else
+ clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19,
+ LN_TX_CLK_DIV2_EN);
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19,
+ LN_TX_CLK_DIV2_EN_OV);
+
+ clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19,
+ LN_TX_CLK_DIV2_RST);
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19,
+ LN_TX_CLK_DIV2_RST_OV);
+
+ clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL12, LN_TX_HRCLK_SEL);
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL12, LN_TX_HRCLK_SEL_OV);
+
+ clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL17, LN_TX_MARGIN);
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL17, LN_TX_MARGIN_OV);
+ clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL17, LN_TX_MARGIN_LSB);
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL17, LN_TX_MARGIN_LSB_OV);
+ clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL17, LN_TX_MARGIN_P1);
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL17, LN_TX_MARGIN_P1_OV);
+ clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL17,
+ LN_TX_MARGIN_P1_LSB);
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL17,
+ LN_TX_MARGIN_P1_LSB_OV);
+
+ clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18, LN_TX_P1_CODE);
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18, LN_TX_P1_CODE_OV);
+ clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18, LN_TX_P1_LSB_CODE);
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18, LN_TX_P1_LSB_CODE_OV);
+ clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18, LN_TX_MARGIN_PRE);
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18, LN_TX_MARGIN_PRE_OV);
+ clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18,
+ LN_TX_MARGIN_PRE_LSB);
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18,
+ LN_TX_MARGIN_PRE_LSB_OV);
+ clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18, LN_TX_PRE_LSB_CODE);
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18,
+ LN_TX_PRE_LSB_CODE_OV);
+ clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18, LN_TX_PRE_CODE);
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18, LN_TX_PRE_CODE_OV);
+
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL11, LN_DTVREG_SML_EN);
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL11, LN_DTVREG_SML_EN_OV);
+ udelay(2);
+
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL11, LN_DTVREG_BIG_EN);
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL11, LN_DTVREG_BIG_EN_OV);
+ udelay(2);
+
+ mask32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL10, LN_DTVREG_ADJUST,
+ FIELD_PREP(LN_DTVREG_ADJUST, 0xa));
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL13, LN_DTVREG_ADJUST_OV);
+ udelay(2);
+
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19, LN_TX_EN);
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19, LN_TX_EN_OV);
+ udelay(2);
+
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_CTLE_CTRL0, LN_TX_CLK_EN);
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_CTLE_CTRL0, LN_TX_CLK_EN_OV);
+
+ clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL12,
+ LN_TX_BYTECLK_RESET_SYNC_CLR);
+ set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL12,
+ LN_TX_BYTECLK_RESET_SYNC_CLR_OV);
+
+ return 0;
+}
+
+static int atcphy_auspll_apb_command(struct apple_atcphy *atcphy, u32 command)
+{
+ int ret;
+ u32 reg;
+
+ reg = readl(atcphy->regs.core + AUSPLL_APB_CMD_OVERRIDE);
+ reg &= ~AUSPLL_APB_CMD_OVERRIDE_CMD;
+ reg |= FIELD_PREP(AUSPLL_APB_CMD_OVERRIDE_CMD, command);
+ reg |= AUSPLL_APB_CMD_OVERRIDE_REQ;
+ reg |= AUSPLL_APB_CMD_OVERRIDE_UNK28;
+ writel(reg, atcphy->regs.core + AUSPLL_APB_CMD_OVERRIDE);
+
+ ret = readl_poll_timeout(atcphy->regs.core + AUSPLL_APB_CMD_OVERRIDE,
+ reg, (reg & AUSPLL_APB_CMD_OVERRIDE_ACK), 100,
+ 100000);
+ if (ret) {
+ dev_err(atcphy->dev, "AUSPLL APB command was not acked.\n");
+ return ret;
+ }
+
+ core_clear32(atcphy, AUSPLL_APB_CMD_OVERRIDE,
+ AUSPLL_APB_CMD_OVERRIDE_REQ);
+
+ return 0;
+}
+
+static int atcphy_dp_configure(struct apple_atcphy *atcphy,
+ enum atcphy_dp_link_rate lr)
+{
+ const struct atcphy_dp_link_rate_configuration *cfg = &dp_lr_config[lr];
+ const struct atcphy_mode_configuration *mode_cfg;
+ int ret;
+ u32 reg;
+
+ trace_atcphy_dp_configure(atcphy, lr);
+
+ if (atcphy->dp_link_rate == lr)
+ return 0;
+
+ if (atcphy->swap_lanes)
+ mode_cfg = &atcphy_modes[atcphy->mode].swapped;
+ else
+ mode_cfg = &atcphy_modes[atcphy->mode].normal;
+
+ core_clear32(atcphy, AUSPLL_FREQ_CFG, AUSPLL_FREQ_REFCLK);
+
+ core_mask32(atcphy, AUSPLL_FREQ_DESC_A, AUSPLL_FD_FREQ_COUNT_TARGET,
+ FIELD_PREP(AUSPLL_FD_FREQ_COUNT_TARGET,
+ cfg->freqinit_count_target));
+ core_clear32(atcphy, AUSPLL_FREQ_DESC_A, AUSPLL_FD_FBDIVN_HALF);
+ core_clear32(atcphy, AUSPLL_FREQ_DESC_A, AUSPLL_FD_REV_DIVN);
+ core_mask32(atcphy, AUSPLL_FREQ_DESC_A, AUSPLL_FD_KI_MAN,
+ FIELD_PREP(AUSPLL_FD_KI_MAN, 8));
+ core_mask32(atcphy, AUSPLL_FREQ_DESC_A, AUSPLL_FD_KI_EXP,
+ FIELD_PREP(AUSPLL_FD_KI_EXP, 3));
+ core_mask32(atcphy, AUSPLL_FREQ_DESC_A, AUSPLL_FD_KP_MAN,
+ FIELD_PREP(AUSPLL_FD_KP_MAN, 8));
+ core_mask32(atcphy, AUSPLL_FREQ_DESC_A, AUSPLL_FD_KP_EXP,
+ FIELD_PREP(AUSPLL_FD_KP_EXP, 7));
+ core_clear32(atcphy, AUSPLL_FREQ_DESC_A, AUSPLL_FD_KPKI_SCALE_HBW);
+
+ core_mask32(atcphy, AUSPLL_FREQ_DESC_B, AUSPLL_FD_FBDIVN_FRAC_DEN,
+ FIELD_PREP(AUSPLL_FD_FBDIVN_FRAC_DEN,
+ cfg->fbdivn_frac_den));
+ core_mask32(atcphy, AUSPLL_FREQ_DESC_B, AUSPLL_FD_FBDIVN_FRAC_NUM,
+ FIELD_PREP(AUSPLL_FD_FBDIVN_FRAC_NUM,
+ cfg->fbdivn_frac_num));
+
+ core_clear32(atcphy, AUSPLL_FREQ_DESC_C, AUSPLL_FD_SDM_SSC_STEP);
+ core_clear32(atcphy, AUSPLL_FREQ_DESC_C, AUSPLL_FD_SDM_SSC_EN);
+ core_mask32(atcphy, AUSPLL_FREQ_DESC_C, AUSPLL_FD_PCLK_DIV_SEL,
+ FIELD_PREP(AUSPLL_FD_PCLK_DIV_SEL, cfg->pclk_div_sel));
+ core_mask32(atcphy, AUSPLL_FREQ_DESC_C, AUSPLL_FD_LFSDM_DIV,
+ FIELD_PREP(AUSPLL_FD_LFSDM_DIV, 1));
+ core_mask32(atcphy, AUSPLL_FREQ_DESC_C, AUSPLL_FD_LFCLK_CTRL,
+ FIELD_PREP(AUSPLL_FD_LFCLK_CTRL, cfg->lfclk_ctrl));
+ core_mask32(atcphy, AUSPLL_FREQ_DESC_C, AUSPLL_FD_VCLK_OP_DIVN,
+ FIELD_PREP(AUSPLL_FD_VCLK_OP_DIVN, cfg->vclk_op_divn));
+ core_set32(atcphy, AUSPLL_FREQ_DESC_C, AUSPLL_FD_VCLK_PRE_DIVN);
+
+ core_mask32(atcphy, AUSPLL_CLKOUT_DIV, AUSPLL_CLKOUT_PLLA_REFBUFCLK_DI,
+ FIELD_PREP(AUSPLL_CLKOUT_PLLA_REFBUFCLK_DI, 7));
+
+ if (cfg->plla_clkout_vreg_bypass)
+ core_set32(atcphy, AUSPLL_CLKOUT_DTC_VREG,
+ AUSPLL_DTC_VREG_BYPASS);
+ else
+ core_clear32(atcphy, AUSPLL_CLKOUT_DTC_VREG,
+ AUSPLL_DTC_VREG_BYPASS);
+
+ core_set32(atcphy, AUSPLL_BGR, AUSPLL_BGR_CTRL_AVAIL);
+
+ core_set32(atcphy, AUSPLL_CLKOUT_MASTER,
+ AUSPLL_CLKOUT_MASTER_PCLK_DRVR_EN);
+ core_set32(atcphy, AUSPLL_CLKOUT_MASTER,
+ AUSPLL_CLKOUT_MASTER_PCLK2_DRVR_EN);
+ core_set32(atcphy, AUSPLL_CLKOUT_MASTER,
+ AUSPLL_CLKOUT_MASTER_REFBUFCLK_DRVR_EN);
+
+ ret = atcphy_auspll_apb_command(atcphy, 0);
+ if (ret)
+ return ret;
+
+ ret = readl_poll_timeout(atcphy->regs.core + ACIOPHY_DP_PCLK_STAT, reg,
+ (reg & ACIOPHY_AUSPLL_LOCK), 100, 100000);
+ if (ret) {
+ dev_err(atcphy->dev, "ACIOPHY_DP_PCLK did not lock.\n");
+ return ret;
+ }
+
+ ret = atcphy_auspll_apb_command(atcphy, 0x2800);
+ if (ret)
+ return ret;
+
+ if (mode_cfg->dp_lane[0]) {
+ ret = atcphy_dp_configure_lane(atcphy, 0, cfg);
+ if (ret)
+ return ret;
+ }
+
+ if (mode_cfg->dp_lane[1]) {
+ ret = atcphy_dp_configure_lane(atcphy, 1, cfg);
+ if (ret)
+ return ret;
+ }
+
+ core_clear32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0,
+ DP_PMA_BYTECLK_RESET);
+ core_clear32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0,
+ DP_MAC_DIV20_CLK_SEL);
+
+ atcphy->dp_link_rate = lr;
+ return 0;
+}
+
+static int atcphy_cio_configure(struct apple_atcphy *atcphy,
+ enum atcphy_mode mode)
+{
+ int ret;
+
+ BUG_ON(!mutex_is_locked(&atcphy->lock));
+
+ ret = atcphy_cio_power_on(atcphy);
+ if (ret)
+ return ret;
+
+ atcphy_setup_pll_fuses(atcphy);
+ atcphy_apply_tunables(atcphy, mode);
+
+ // TODO: without this sometimes device aren't recognized but no idea what it does
+ // ACIOPHY_PLL_TOP_BLK_AUSPLL_PCTL_FSM_CTRL1.APB_REQ_OV_SEL = 255
+ core_set32(atcphy, 0x1014, 255 << 13);
+ core_set32(atcphy, AUSPLL_APB_CMD_OVERRIDE,
+ AUSPLL_APB_CMD_OVERRIDE_UNK28);
+
+ writel(0x10000cef, atcphy->regs.core + 0x8); // ACIOPHY_CFG0
+ writel(0x15570cff, atcphy->regs.core + 0x1b0); // ACIOPHY_SLEEP_CTRL
+ writel(0x11833fef, atcphy->regs.core + 0x8); // ACIOPHY_CFG0
+
+ /* enable clocks and configure lanes */
+ core_set32(atcphy, CIO3PLL_CLK_CTRL, CIO3PLL_CLK_PCLK_EN);
+ core_set32(atcphy, CIO3PLL_CLK_CTRL, CIO3PLL_CLK_REFCLK_EN);
+ atcphy_configure_lanes(atcphy, mode);
+
+ /* take the USB3 PHY out of reset */
+ core_set32(atcphy, ATCPHY_POWER_CTRL, ATCPHY_POWER_PHY_RESET_N);
+
+ /* setup AUX channel if DP altmode is requested */
+ if (atcphy_modes[mode].enable_dp_aux)
+ atcphy_enable_dp_aux(atcphy);
+
+ atcphy->mode = mode;
+ return 0;
+}
+
+static int atcphy_usb3_power_on(struct phy *phy)
+{
+ struct apple_atcphy *atcphy = phy_get_drvdata(phy);
+ enum atcphy_pipehandler_state state;
+ int ret = 0;
+
+ /*
+ * Both usb role switch and mux set work will be running concurrently.
+ * Make sure atcphy_mux_set_work is done bringing up ATCPHY before
+ * trying to switch dwc3 to the correct PHY.
+ */
+ mutex_lock(&atcphy->lock);
+ if (atcphy->mode != atcphy->target_mode) {
+ reinit_completion(&atcphy->atcphy_online_event);
+ mutex_unlock(&atcphy->lock);
+ wait_for_completion_timeout(&atcphy->atcphy_online_event,
+ msecs_to_jiffies(1000));
+ mutex_lock(&atcphy->lock);
+ }
+
+ if (atcphy->mode != atcphy->target_mode) {
+ dev_err(atcphy->dev, "ATCPHY did not come up; won't allow dwc3 to come up.\n");
+ return -EINVAL;
+ }
+
+ atcphy->dwc3_online = true;
+ state = atcphy_modes[atcphy->mode].pipehandler_state;
+ switch (state) {
+ case ATCPHY_PIPEHANDLER_STATE_USB2:
+ case ATCPHY_PIPEHANDLER_STATE_USB3:
+ ret = atcphy_configure_pipehandler(atcphy, state);
+ break;
+
+ case ATCPHY_PIPEHANDLER_STATE_INVALID:
+ default:
+ dev_warn(atcphy->dev, "Invalid state %d in usb3_set_phy\n",
+ state);
+ ret = -EINVAL;
+ }
+
+ mutex_unlock(&atcphy->lock);
+
+ return 0;
+}
+
+static int atcphy_usb3_power_off(struct phy *phy)
+{
+ struct apple_atcphy *atcphy = phy_get_drvdata(phy);
+
+ mutex_lock(&atcphy->lock);
+
+ atcphy_configure_pipehandler(atcphy, ATCPHY_PIPEHANDLER_STATE_USB2);
+
+ atcphy->dwc3_online = false;
+ complete(&atcphy->dwc3_shutdown_event);
+
+ mutex_unlock(&atcphy->lock);
+
+ return 0;
+}
+
+static const struct phy_ops apple_atc_usb3_phy_ops = {
+ .owner = THIS_MODULE,
+ .power_on = atcphy_usb3_power_on,
+ .power_off = atcphy_usb3_power_off,
+};
+
+static int atcphy_usb2_power_on(struct phy *phy)
+{
+ struct apple_atcphy *atcphy = phy_get_drvdata(phy);
+
+ mutex_lock(&atcphy->lock);
+
+ /* take the PHY out of its low power state */
+ clear32(atcphy->regs.usb2phy + USB2PHY_CTL, USB2PHY_CTL_SIDDQ);
+ udelay(10);
+
+ /* reset the PHY for good measure */
+ clear32(atcphy->regs.usb2phy + USB2PHY_CTL, USB2PHY_CTL_APB_RESET_N);
+ set32(atcphy->regs.usb2phy + USB2PHY_CTL,
+ USB2PHY_CTL_RESET | USB2PHY_CTL_PORT_RESET);
+ udelay(10);
+ set32(atcphy->regs.usb2phy + USB2PHY_CTL, USB2PHY_CTL_APB_RESET_N);
+ clear32(atcphy->regs.usb2phy + USB2PHY_CTL,
+ USB2PHY_CTL_RESET | USB2PHY_CTL_PORT_RESET);
+
+ set32(atcphy->regs.usb2phy + USB2PHY_SIG,
+ USB2PHY_SIG_VBUSDET_FORCE_VAL | USB2PHY_SIG_VBUSDET_FORCE_EN |
+ USB2PHY_SIG_VBUSVLDEXT_FORCE_VAL |
+ USB2PHY_SIG_VBUSVLDEXT_FORCE_EN);
+
+ /* enable the dummy PHY for the SS lanes */
+ set32(atcphy->regs.pipehandler + PIPEHANDLER_NONSELECTED_OVERRIDE,
+ PIPEHANDLER_DUMMY_PHY_EN);
+
+ mutex_unlock(&atcphy->lock);
+
+ return 0;
+}
+
+static int atcphy_usb2_power_off(struct phy *phy)
+{
+ struct apple_atcphy *atcphy = phy_get_drvdata(phy);
+
+ mutex_lock(&atcphy->lock);
+
+ /* reset the PHY before transitioning to low power mode */
+ clear32(atcphy->regs.usb2phy + USB2PHY_CTL, USB2PHY_CTL_APB_RESET_N);
+ set32(atcphy->regs.usb2phy + USB2PHY_CTL,
+ USB2PHY_CTL_RESET | USB2PHY_CTL_PORT_RESET);
+
+ /* switch the PHY to low power mode */
+ set32(atcphy->regs.usb2phy + USB2PHY_CTL, USB2PHY_CTL_SIDDQ);
+
+ mutex_unlock(&atcphy->lock);
+
+ return 0;
+}
+
+static int atcphy_usb2_set_mode(struct phy *phy, enum phy_mode mode,
+ int submode)
+{
+ struct apple_atcphy *atcphy = phy_get_drvdata(phy);
+ int ret;
+
+ mutex_lock(&atcphy->lock);
+
+ switch (mode) {
+ case PHY_MODE_USB_HOST:
+ case PHY_MODE_USB_HOST_LS:
+ case PHY_MODE_USB_HOST_FS:
+ case PHY_MODE_USB_HOST_HS:
+ case PHY_MODE_USB_HOST_SS:
+ set32(atcphy->regs.usb2phy + USB2PHY_SIG, USB2PHY_SIG_HOST);
+ set32(atcphy->regs.usb2phy + USB2PHY_USBCTL,
+ USB2PHY_USBCTL_HOST_EN);
+ ret = 0;
+ break;
+
+ case PHY_MODE_USB_DEVICE:
+ case PHY_MODE_USB_DEVICE_LS:
+ case PHY_MODE_USB_DEVICE_FS:
+ case PHY_MODE_USB_DEVICE_HS:
+ case PHY_MODE_USB_DEVICE_SS:
+ clear32(atcphy->regs.usb2phy + USB2PHY_SIG, USB2PHY_SIG_HOST);
+ clear32(atcphy->regs.usb2phy + USB2PHY_USBCTL,
+ USB2PHY_USBCTL_HOST_EN);
+ ret = 0;
+ break;
+
+ default:
+ dev_err(atcphy->dev, "Unknown mode for usb2 phy: %d\n", mode);
+ ret = -EINVAL;
+ }
+
+ mutex_unlock(&atcphy->lock);
+ return ret;
+}
+
+static const struct phy_ops apple_atc_usb2_phy_ops = {
+ .owner = THIS_MODULE,
+ .set_mode = atcphy_usb2_set_mode,
+ /*
+ * This PHY is always matched with a dwc3 controller. Currently,
+ * first dwc3 initializes the PHY and then soft-resets itself and
+ * then finally powers on the PHY. This should be reasonable.
+ * Annoyingly, the dwc3 soft reset is never completed when the USB2 PHY
+ * is powered off so we have to pretend that these two are actually
+ * init/exit here to ensure the PHY is powered on and out of reset
+ * early enough.
+ */
+ .init = atcphy_usb2_power_on,
+ .exit = atcphy_usb2_power_off,
+};
+
+static int atcphy_dpphy_set_mode(struct phy *phy, enum phy_mode mode,
+ int submode)
+{
+ /* nothing to do here since the setup already happened in mux_set */
+ if (mode == PHY_MODE_DP && submode == 0)
+ return 0;
+ return -EINVAL;
+}
+
+static int atcphy_dpphy_validate(struct phy *phy, enum phy_mode mode,
+ int submode, union phy_configure_opts *opts_)
+{
+ struct phy_configure_opts_dp *opts = &opts_->dp;
+ struct apple_atcphy *atcphy = phy_get_drvdata(phy);
+
+ if (mode != PHY_MODE_DP)
+ return -EINVAL;
+ if (submode != 0)
+ return -EINVAL;
+
+ switch (atcphy->mode) {
+ case APPLE_ATCPHY_MODE_USB3_DP:
+ opts->lanes = 2;
+ break;
+ case APPLE_ATCPHY_MODE_DP:
+ opts->lanes = 4;
+ break;
+ default:
+ opts->lanes = 0;
+ }
+
+ opts->link_rate = 8100;
+
+ for (int i = 0; i < 4; ++i) {
+ opts->voltage[i] = 3;
+ opts->pre[i] = 3;
+ }
+
+ return 0;
+}
+
+static int atcphy_dpphy_configure(struct phy *phy,
+ union phy_configure_opts *opts_)
+{
+ struct phy_configure_opts_dp *opts = &opts_->dp;
+ struct apple_atcphy *atcphy = phy_get_drvdata(phy);
+ enum atcphy_dp_link_rate link_rate;
+ int ret = 0;
+
+ /* might be possibly but we don't know how */
+ if (opts->set_voltages)
+ return -EINVAL;
+
+ /* TODO? or maybe just ack since this mux_set should've done this? */
+ if (opts->set_lanes)
+ return -EINVAL;
+
+ if (opts->set_rate) {
+ switch (opts->link_rate) {
+ case 1620:
+ link_rate = ATCPHY_DP_LINK_RATE_RBR;
+ break;
+ case 2700:
+ link_rate = ATCPHY_DP_LINK_RATE_HBR;
+ break;
+ case 5400:
+ link_rate = ATCPHY_DP_LINK_RATE_HBR2;
+ break;
+ case 8100:
+ link_rate = ATCPHY_DP_LINK_RATE_HBR3;
+ break;
+ case 0:
+ // TODO: disable!
+ return 0;
+ break;
+ default:
+ dev_err(atcphy->dev, "Unsupported link rate: %d\n",
+ opts->link_rate);
+ return -EINVAL;
+ }
+
+ mutex_lock(&atcphy->lock);
+ ret = atcphy_dp_configure(atcphy, link_rate);
+ mutex_unlock(&atcphy->lock);
+ }
+
+ return ret;
+}
+
+static const struct phy_ops apple_atc_dp_phy_ops = {
+ .owner = THIS_MODULE,
+ .configure = atcphy_dpphy_configure,
+ .validate = atcphy_dpphy_validate,
+ .set_mode = atcphy_dpphy_set_mode,
+};
+
+static struct phy *atcphy_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct apple_atcphy *atcphy = dev_get_drvdata(dev);
+
+ switch (args->args[0]) {
+ case PHY_TYPE_USB2:
+ return atcphy->phy_usb2;
+ case PHY_TYPE_USB3:
+ return atcphy->phy_usb3;
+ case PHY_TYPE_DP:
+ return atcphy->phy_dp;
+ }
+ return ERR_PTR(-ENODEV);
+}
+
+static int atcphy_probe_phy(struct apple_atcphy *atcphy)
+{
+ atcphy->phy_usb2 =
+ devm_phy_create(atcphy->dev, NULL, &apple_atc_usb2_phy_ops);
+ if (IS_ERR(atcphy->phy_usb2))
+ return PTR_ERR(atcphy->phy_usb2);
+ phy_set_drvdata(atcphy->phy_usb2, atcphy);
+
+ atcphy->phy_usb3 =
+ devm_phy_create(atcphy->dev, NULL, &apple_atc_usb3_phy_ops);
+ if (IS_ERR(atcphy->phy_usb3))
+ return PTR_ERR(atcphy->phy_usb3);
+ phy_set_drvdata(atcphy->phy_usb3, atcphy);
+
+ atcphy->phy_dp =
+ devm_phy_create(atcphy->dev, NULL, &apple_atc_dp_phy_ops);
+ if (IS_ERR(atcphy->phy_dp))
+ return PTR_ERR(atcphy->phy_dp);
+ phy_set_drvdata(atcphy->phy_dp, atcphy);
+
+ atcphy->phy_provider =
+ devm_of_phy_provider_register(atcphy->dev, atcphy_xlate);
+ if (IS_ERR(atcphy->phy_provider))
+ return PTR_ERR(atcphy->phy_provider);
+
+ return 0;
+}
+
+static int atcphy_dwc3_reset_assert(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ struct apple_atcphy *atcphy = rcdev_to_apple_atcphy(rcdev);
+
+ clear32(atcphy->regs.pipehandler + PIPEHANDLER_AON_GEN,
+ PIPEHANDLER_AON_GEN_DWC3_RESET_N);
+ set32(atcphy->regs.pipehandler + PIPEHANDLER_AON_GEN,
+ PIPEHANDLER_AON_GEN_DWC3_FORCE_CLAMP_EN);
+
+ return 0;
+}
+
+static int atcphy_dwc3_reset_deassert(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ struct apple_atcphy *atcphy = rcdev_to_apple_atcphy(rcdev);
+
+ clear32(atcphy->regs.pipehandler + PIPEHANDLER_AON_GEN,
+ PIPEHANDLER_AON_GEN_DWC3_FORCE_CLAMP_EN);
+ set32(atcphy->regs.pipehandler + PIPEHANDLER_AON_GEN,
+ PIPEHANDLER_AON_GEN_DWC3_RESET_N);
+
+ return 0;
+}
+
+const struct reset_control_ops atcphy_dwc3_reset_ops = {
+ .assert = atcphy_dwc3_reset_assert,
+ .deassert = atcphy_dwc3_reset_deassert,
+};
+
+static int atcphy_reset_xlate(struct reset_controller_dev *rcdev,
+ const struct of_phandle_args *reset_spec)
+{
+ return 0;
+}
+
+static int atcphy_probe_rcdev(struct apple_atcphy *atcphy)
+{
+ atcphy->rcdev.owner = THIS_MODULE;
+ atcphy->rcdev.nr_resets = 1;
+ atcphy->rcdev.ops = &atcphy_dwc3_reset_ops;
+ atcphy->rcdev.of_node = atcphy->dev->of_node;
+ atcphy->rcdev.of_reset_n_cells = 0;
+ atcphy->rcdev.of_xlate = atcphy_reset_xlate;
+
+ return devm_reset_controller_register(atcphy->dev, &atcphy->rcdev);
+}
+
+static int atcphy_sw_set(struct typec_switch_dev *sw,
+ enum typec_orientation orientation)
+{
+ struct apple_atcphy *atcphy = typec_switch_get_drvdata(sw);
+
+ trace_atcphy_sw_set(orientation);
+
+ mutex_lock(&atcphy->lock);
+ switch (orientation) {
+ case TYPEC_ORIENTATION_NONE:
+ break;
+ case TYPEC_ORIENTATION_NORMAL:
+ atcphy->swap_lanes = false;
+ break;
+ case TYPEC_ORIENTATION_REVERSE:
+ atcphy->swap_lanes = true;
+ break;
+ }
+ mutex_unlock(&atcphy->lock);
+
+ return 0;
+}
+
+static int atcphy_probe_switch(struct apple_atcphy *atcphy)
+{
+ struct typec_switch_desc sw_desc = {
+ .drvdata = atcphy,
+ .fwnode = atcphy->dev->fwnode,
+ .set = atcphy_sw_set,
+ };
+
+ return PTR_ERR_OR_ZERO(typec_switch_register(atcphy->dev, &sw_desc));
+}
+
+static void atcphy_mux_set_work(struct work_struct *work)
+{
+ struct apple_atcphy *atcphy = container_of(work, struct apple_atcphy, mux_set_work);
+
+ mutex_lock(&atcphy->lock);
+ /*
+ * If we're transitiong to TYPEC_STATE_SAFE dwc3 will have gotten
+ * a usb-role-switch event to ROLE_NONE which is deferred to a work
+ * queue. dwc3 will try to switch the pipehandler mux to USB2 and
+ * we have to make sure that has happened before we disable ATCPHY.
+ * If we instead disable ATCPHY first dwc3 will get stuck and the
+ * port won't work anymore until a full SoC reset.
+ * We're guaranteed that no other role switch event will be generated
+ * before we return because the mux_set callback runs in the same
+ * thread that generates these. We can thus unlock the mutex, wait
+ * for dwc3_shutdown_event from the usb3 phy's power_off callback after
+ * it has taken the mutex and the lock again.
+ */
+ if (atcphy->dwc3_online && atcphy->target_mode == APPLE_ATCPHY_MODE_OFF) {
+ reinit_completion(&atcphy->dwc3_shutdown_event);
+ mutex_unlock(&atcphy->lock);
+ wait_for_completion_timeout(&atcphy->dwc3_shutdown_event,
+ msecs_to_jiffies(1000));
+ mutex_lock(&atcphy->lock);
+ WARN_ON(atcphy->dwc3_online);
+ }
+
+ switch (atcphy->target_mode) {
+ case APPLE_ATCPHY_MODE_DP:
+ case APPLE_ATCPHY_MODE_USB3_DP:
+ case APPLE_ATCPHY_MODE_USB3:
+ case APPLE_ATCPHY_MODE_USB4:
+ atcphy_cio_configure(atcphy, atcphy->target_mode);
+ break;
+ default:
+ dev_warn(atcphy->dev, "Unknown mode %d in atcphy_mux_set\n",
+ atcphy->target_mode);
+ fallthrough;
+ case APPLE_ATCPHY_MODE_USB2:
+ case APPLE_ATCPHY_MODE_OFF:
+ atcphy->mode = APPLE_ATCPHY_MODE_OFF;
+ atcphy_disable_dp_aux(atcphy);
+ atcphy_cio_power_off(atcphy);
+ }
+
+ complete(&atcphy->atcphy_online_event);
+ mutex_unlock(&atcphy->lock);
+}
+
+static int atcphy_mux_set(struct typec_mux_dev *mux,
+ struct typec_mux_state *state)
+{
+ struct apple_atcphy *atcphy = typec_mux_get_drvdata(mux);
+
+ // TODO:
+ flush_work(&atcphy->mux_set_work);
+
+ mutex_lock(&atcphy->lock);
+ trace_atcphy_mux_set(state);
+
+ if (state->mode == TYPEC_STATE_SAFE) {
+ atcphy->target_mode = APPLE_ATCPHY_MODE_OFF;
+ } else if (state->mode == TYPEC_STATE_USB) {
+ atcphy->target_mode = APPLE_ATCPHY_MODE_USB3;
+ } else if (state->alt && state->alt->svid == USB_TYPEC_DP_SID) {
+ switch (state->mode) {
+ case TYPEC_DP_STATE_C:
+ case TYPEC_DP_STATE_E:
+ atcphy->target_mode = APPLE_ATCPHY_MODE_DP;
+ break;
+ case TYPEC_DP_STATE_D:
+ atcphy->target_mode = APPLE_ATCPHY_MODE_USB3_DP;
+ break;
+ default:
+ dev_err(atcphy->dev,
+ "Unsupported DP pin assignment: 0x%lx.\n",
+ state->mode);
+ atcphy->target_mode = APPLE_ATCPHY_MODE_OFF;
+ }
+ } else if (state->alt && state->alt->svid == USB_TYPEC_TBT_SID) {
+ dev_err(atcphy->dev, "USB4/TBT mode is not supported yet.\n");
+ atcphy->target_mode = APPLE_ATCPHY_MODE_OFF;
+ } else if (state->alt) {
+ dev_err(atcphy->dev, "Unknown alternate mode SVID: 0x%x\n",
+ state->alt->svid);
+ atcphy->target_mode = APPLE_ATCPHY_MODE_OFF;
+ } else {
+ dev_err(atcphy->dev, "Unknown mode: 0x%lx\n", state->mode);
+ atcphy->target_mode = APPLE_ATCPHY_MODE_OFF;
+ }
+
+ if (atcphy->mode != atcphy->target_mode)
+ WARN_ON(!schedule_work(&atcphy->mux_set_work));
+
+ mutex_unlock(&atcphy->lock);
+
+ return 0;
+}
+
+static int atcphy_probe_mux(struct apple_atcphy *atcphy)
+{
+ struct typec_mux_desc mux_desc = {
+ .drvdata = atcphy,
+ .fwnode = atcphy->dev->fwnode,
+ .set = atcphy_mux_set,
+ };
+
+ return PTR_ERR_OR_ZERO(typec_mux_register(atcphy->dev, &mux_desc));
+}
+
+static int atcphy_parse_legacy_tunable(struct apple_atcphy *atcphy,
+ struct atcphy_tunable *tunable,
+ const char *name)
+{
+ struct property *prop;
+ const __le32 *p = NULL;
+ int i;
+
+#if 0
+ WARN_TAINT_ONCE(1, TAINT_FIRMWARE_WORKAROUND,
+ "parsing legacy tunable; please update m1n1");
+#endif
+
+ prop = of_find_property(atcphy->np, name, NULL);
+ if (!prop) {
+ dev_err(atcphy->dev, "tunable %s not found\n", name);
+ return -ENOENT;
+ }
+
+ if (prop->length % (3 * sizeof(u32)))
+ return -EINVAL;
+
+ tunable->sz = prop->length / (3 * sizeof(u32));
+ tunable->values = devm_kcalloc(atcphy->dev, tunable->sz,
+ sizeof(*tunable->values), GFP_KERNEL);
+ if (!tunable->values)
+ return -ENOMEM;
+
+ for (i = 0; i < tunable->sz; ++i) {
+ p = of_prop_next_u32(prop, p, &tunable->values[i].offset);
+ p = of_prop_next_u32(prop, p, &tunable->values[i].mask);
+ p = of_prop_next_u32(prop, p, &tunable->values[i].value);
+ }
+
+ trace_atcphy_parsed_tunable(name, tunable);
+
+ return 0;
+}
+
+static int atcphy_parse_new_tunable(struct apple_atcphy *atcphy,
+ struct atcphy_tunable *tunable,
+ const char *name)
+{
+ struct property *prop;
+ u64 *fdt_tunable;
+ int ret, i;
+
+ prop = of_find_property(atcphy->np, name, NULL);
+ if (!prop) {
+ dev_err(atcphy->dev, "tunable %s not found\n", name);
+ return -ENOENT;
+ }
+
+ if (prop->length % (4 * sizeof(u64)))
+ return -EINVAL;
+
+ fdt_tunable = kzalloc(prop->length, GFP_KERNEL);
+ if (!fdt_tunable)
+ return -ENOMEM;
+
+ tunable->sz = prop->length / (4 * sizeof(u64));
+ ret = of_property_read_variable_u64_array(atcphy->np, name, fdt_tunable,
+ tunable->sz, tunable->sz);
+ if (ret < 0)
+ goto err_free_fdt;
+
+ tunable->values = devm_kcalloc(atcphy->dev, tunable->sz,
+ sizeof(*tunable->values), GFP_KERNEL);
+ if (!tunable->values) {
+ ret = -ENOMEM;
+ goto err_free_fdt;
+ }
+
+ for (i = 0; i < tunable->sz; ++i) {
+ u32 offset, size, mask, value;
+
+ offset = fdt_tunable[4 * i];
+ size = fdt_tunable[4 * i + 1];
+ mask = fdt_tunable[4 * i + 2];
+ value = fdt_tunable[4 * i + 3];
+
+ if (offset > U32_MAX || size != 4 || mask > U32_MAX ||
+ value > U32_MAX) {
+ ret = -EINVAL;
+ goto err_free_values;
+ }
+
+ tunable->values[i].offset = offset;
+ tunable->values[i].mask = mask;
+ tunable->values[i].value = value;
+ }
+
+ trace_atcphy_parsed_tunable(name, tunable);
+ kfree(fdt_tunable);
+
+ BUG_ON(1);
+ return 0;
+
+err_free_values:
+ devm_kfree(atcphy->dev, tunable->values);
+err_free_fdt:
+ kfree(fdt_tunable);
+ return ret;
+}
+
+static int atcphy_parse_tunable(struct apple_atcphy *atcphy,
+ struct atcphy_tunable *tunable,
+ const char *name)
+{
+ int ret;
+
+ if (!of_find_property(atcphy->np, name, NULL)) {
+ dev_err(atcphy->dev, "tunable %s not found\n", name);
+ return -ENOENT;
+ }
+
+ ret = atcphy_parse_new_tunable(atcphy, tunable, name);
+ if (ret)
+ ret = atcphy_parse_legacy_tunable(atcphy, tunable, name);
+
+ return ret;
+}
+
+static int atcphy_load_tunables(struct apple_atcphy *atcphy)
+{
+ int ret;
+
+ ret = atcphy_parse_tunable(atcphy, &atcphy->tunables.axi2af,
+ "apple,tunable-axi2af");
+ if (ret)
+ return ret;
+ ret = atcphy_parse_tunable(atcphy, &atcphy->tunables.common,
+ "apple,tunable-common");
+ if (ret)
+ return ret;
+ ret = atcphy_parse_tunable(atcphy, &atcphy->tunables.lane_usb3[0],
+ "apple,tunable-lane0-usb");
+ if (ret)
+ return ret;
+ ret = atcphy_parse_tunable(atcphy, &atcphy->tunables.lane_usb3[1],
+ "apple,tunable-lane1-usb");
+ if (ret)
+ return ret;
+ ret = atcphy_parse_tunable(atcphy, &atcphy->tunables.lane_usb4[0],
+ "apple,tunable-lane0-cio");
+ if (ret)
+ return ret;
+ ret = atcphy_parse_tunable(atcphy, &atcphy->tunables.lane_usb4[1],
+ "apple,tunable-lane1-cio");
+ if (ret)
+ return ret;
+ ret = atcphy_parse_tunable(atcphy,
+ &atcphy->tunables.lane_displayport[0],
+ "apple,tunable-lane0-dp");
+ if (ret)
+ return ret;
+ ret = atcphy_parse_tunable(atcphy,
+ &atcphy->tunables.lane_displayport[1],
+ "apple,tunable-lane1-dp");
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int atcphy_load_fuses(struct apple_atcphy *atcphy)
+{
+ int ret;
+
+ ret = nvmem_cell_read_variable_le_u32(
+ atcphy->dev, "aus_cmn_shm_vreg_trim",
+ &atcphy->fuses.aus_cmn_shm_vreg_trim);
+ if (ret)
+ return ret;
+ ret = nvmem_cell_read_variable_le_u32(
+ atcphy->dev, "auspll_rodco_encap",
+ &atcphy->fuses.auspll_rodco_encap);
+ if (ret)
+ return ret;
+ ret = nvmem_cell_read_variable_le_u32(
+ atcphy->dev, "auspll_rodco_bias_adjust",
+ &atcphy->fuses.auspll_rodco_bias_adjust);
+ if (ret)
+ return ret;
+ ret = nvmem_cell_read_variable_le_u32(
+ atcphy->dev, "auspll_fracn_dll_start_capcode",
+ &atcphy->fuses.auspll_fracn_dll_start_capcode);
+ if (ret)
+ return ret;
+ ret = nvmem_cell_read_variable_le_u32(
+ atcphy->dev, "auspll_dtc_vreg_adjust",
+ &atcphy->fuses.auspll_dtc_vreg_adjust);
+ if (ret)
+ return ret;
+ ret = nvmem_cell_read_variable_le_u32(
+ atcphy->dev, "cio3pll_dco_coarsebin0",
+ &atcphy->fuses.cio3pll_dco_coarsebin[0]);
+ if (ret)
+ return ret;
+ ret = nvmem_cell_read_variable_le_u32(
+ atcphy->dev, "cio3pll_dco_coarsebin1",
+ &atcphy->fuses.cio3pll_dco_coarsebin[1]);
+ if (ret)
+ return ret;
+ ret = nvmem_cell_read_variable_le_u32(
+ atcphy->dev, "cio3pll_dll_start_capcode",
+ &atcphy->fuses.cio3pll_dll_start_capcode[0]);
+ if (ret)
+ return ret;
+ ret = nvmem_cell_read_variable_le_u32(
+ atcphy->dev, "cio3pll_dtc_vreg_adjust",
+ &atcphy->fuses.cio3pll_dtc_vreg_adjust);
+ if (ret)
+ return ret;
+
+ /*
+ * Only one of the two t8103 PHYs requires the following additional fuse
+ * and a slighly different configuration sequence if it's present.
+ * The other t8103 instance and all t6000 instances don't which means
+ * we must not fail here in case the fuse isn't present.
+ */
+ ret = nvmem_cell_read_variable_le_u32(
+ atcphy->dev, "cio3pll_dll_start_capcode_workaround",
+ &atcphy->fuses.cio3pll_dll_start_capcode[1]);
+ switch (ret) {
+ case 0:
+ atcphy->quirks.t8103_cio3pll_workaround = true;
+ break;
+ case -ENOENT:
+ atcphy->quirks.t8103_cio3pll_workaround = false;
+ break;
+ default:
+ return ret;
+ }
+
+ atcphy->fuses.present = true;
+
+ trace_atcphy_fuses(atcphy);
+ return 0;
+}
+
+static int atcphy_probe(struct platform_device *pdev)
+{
+ struct apple_atcphy *atcphy;
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ atcphy = devm_kzalloc(&pdev->dev, sizeof(*atcphy), GFP_KERNEL);
+ if (!atcphy)
+ return -ENOMEM;
+
+ atcphy->dev = dev;
+ atcphy->np = dev->of_node;
+ platform_set_drvdata(pdev, atcphy);
+
+ mutex_init(&atcphy->lock);
+ init_completion(&atcphy->dwc3_shutdown_event);
+ init_completion(&atcphy->atcphy_online_event);
+ INIT_WORK(&atcphy->mux_set_work, atcphy_mux_set_work);
+
+ atcphy->regs.core = devm_platform_ioremap_resource_byname(pdev, "core");
+ if (IS_ERR(atcphy->regs.core))
+ return PTR_ERR(atcphy->regs.core);
+ atcphy->regs.lpdptx =
+ devm_platform_ioremap_resource_byname(pdev, "lpdptx");
+ if (IS_ERR(atcphy->regs.lpdptx))
+ return PTR_ERR(atcphy->regs.lpdptx);
+ atcphy->regs.axi2af =
+ devm_platform_ioremap_resource_byname(pdev, "axi2af");
+ if (IS_ERR(atcphy->regs.axi2af))
+ return PTR_ERR(atcphy->regs.axi2af);
+ atcphy->regs.usb2phy =
+ devm_platform_ioremap_resource_byname(pdev, "usb2phy");
+ if (IS_ERR(atcphy->regs.usb2phy))
+ return PTR_ERR(atcphy->regs.usb2phy);
+ atcphy->regs.pipehandler =
+ devm_platform_ioremap_resource_byname(pdev, "pipehandler");
+ if (IS_ERR(atcphy->regs.pipehandler))
+ return PTR_ERR(atcphy->regs.pipehandler);
+
+ if (of_property_read_bool(dev->of_node, "nvmem-cells")) {
+ ret = atcphy_load_fuses(atcphy);
+ if (ret)
+ return ret;
+ }
+
+ ret = atcphy_load_tunables(atcphy);
+ if (ret)
+ return ret;
+
+ atcphy->mode = APPLE_ATCPHY_MODE_OFF;
+ atcphy->pipehandler_state = ATCPHY_PIPEHANDLER_STATE_INVALID;
+
+ ret = atcphy_probe_rcdev(atcphy);
+ if (ret)
+ return ret;
+ ret = atcphy_probe_mux(atcphy);
+ if (ret)
+ return ret;
+ ret = atcphy_probe_switch(atcphy);
+ if (ret)
+ return ret;
+ return atcphy_probe_phy(atcphy);
+}
+
+static const struct of_device_id atcphy_match[] = {
+ {
+ .compatible = "apple,t8103-atcphy",
+ },
+ {
+ .compatible = "apple,t6000-atcphy",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, atcphy_match);
+
+static struct platform_driver atcphy_driver = {
+ .driver = {
+ .name = "phy-apple-atc",
+ .of_match_table = atcphy_match,
+ },
+ .probe = atcphy_probe,
+};
+
+module_platform_driver(atcphy_driver);
+
+MODULE_AUTHOR("Sven Peter <sven@svenpeter.dev>");
+MODULE_DESCRIPTION("Apple Type-C PHY driver");
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/phy/apple/atc.h b/drivers/phy/apple/atc.h
new file mode 100644
index 0000000..6b02095
--- /dev/null
+++ b/drivers/phy/apple/atc.h
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+/*
+ * Apple Type-C PHY driver
+ *
+ * Copyright (C) The Asahi Linux Contributors
+ * Author: Sven Peter <sven@svenpeter.dev>
+ */
+
+#ifndef APPLE_PHY_ATC_H
+#define APPLE_PHY_ATC_H 1
+
+#include <linux/mutex.h>
+#include <linux/phy/phy.h>
+#include <linux/usb/typec_mux.h>
+#include <linux/reset-controller.h>
+#include <linux/types.h>
+#include <linux/usb/typec.h>
+#include <linux/usb/typec_altmode.h>
+#include <linux/usb/typec_dp.h>
+#include <linux/usb/typec_tbt.h>
+#include <linux/workqueue.h>
+
+enum atcphy_dp_link_rate {
+ ATCPHY_DP_LINK_RATE_RBR,
+ ATCPHY_DP_LINK_RATE_HBR,
+ ATCPHY_DP_LINK_RATE_HBR2,
+ ATCPHY_DP_LINK_RATE_HBR3,
+};
+
+enum atcphy_pipehandler_state {
+ ATCPHY_PIPEHANDLER_STATE_INVALID,
+ ATCPHY_PIPEHANDLER_STATE_USB2,
+ ATCPHY_PIPEHANDLER_STATE_USB3,
+};
+
+enum atcphy_mode {
+ APPLE_ATCPHY_MODE_OFF,
+ APPLE_ATCPHY_MODE_USB2,
+ APPLE_ATCPHY_MODE_USB3,
+ APPLE_ATCPHY_MODE_USB3_DP,
+ APPLE_ATCPHY_MODE_USB4,
+ APPLE_ATCPHY_MODE_DP,
+};
+
+struct atcphy_dp_link_rate_configuration {
+ u16 freqinit_count_target;
+ u16 fbdivn_frac_den;
+ u16 fbdivn_frac_num;
+ u16 pclk_div_sel;
+ u8 lfclk_ctrl;
+ u8 vclk_op_divn;
+ bool plla_clkout_vreg_bypass;
+ bool bypass_txa_ldoclk;
+ bool txa_div2_en;
+};
+
+struct atcphy_mode_configuration {
+ u32 crossbar;
+ u32 crossbar_dp_single_pma;
+ bool crossbar_dp_both_pma;
+ u32 lane_mode[2];
+ bool dp_lane[2];
+ bool set_swap;
+};
+
+struct atcphy_tunable {
+ size_t sz;
+ struct {
+ u32 offset;
+ u32 mask;
+ u32 value;
+ } * values;
+};
+
+struct apple_atcphy {
+ struct device_node *np;
+ struct device *dev;
+
+ struct {
+ unsigned int t8103_cio3pll_workaround : 1;
+ } quirks;
+
+ /* calibration fuse values */
+ struct {
+ bool present;
+ u32 aus_cmn_shm_vreg_trim;
+ u32 auspll_rodco_encap;
+ u32 auspll_rodco_bias_adjust;
+ u32 auspll_fracn_dll_start_capcode;
+ u32 auspll_dtc_vreg_adjust;
+ u32 cio3pll_dco_coarsebin[2];
+ u32 cio3pll_dll_start_capcode[2];
+ u32 cio3pll_dtc_vreg_adjust;
+ } fuses;
+
+ /* tunables provided by firmware through the device tree */
+ struct {
+ struct atcphy_tunable axi2af;
+ struct atcphy_tunable common;
+ struct atcphy_tunable lane_usb3[2];
+ struct atcphy_tunable lane_displayport[2];
+ struct atcphy_tunable lane_usb4[2];
+ } tunables;
+
+ bool usb3_power_on;
+ bool swap_lanes;
+
+ enum atcphy_mode mode;
+ int dp_link_rate;
+
+ struct {
+ void __iomem *core;
+ void __iomem *axi2af;
+ void __iomem *usb2phy;
+ void __iomem *pipehandler;
+ void __iomem *lpdptx;
+ } regs;
+
+ struct phy *phy_usb2;
+ struct phy *phy_usb3;
+ struct phy *phy_dp;
+ struct phy_provider *phy_provider;
+ struct reset_controller_dev rcdev;
+ struct typec_switch *sw;
+ struct typec_mux *mux;
+
+ bool dwc3_online;
+ struct completion dwc3_shutdown_event;
+ struct completion atcphy_online_event;
+
+ enum atcphy_pipehandler_state pipehandler_state;
+
+ struct mutex lock;
+
+ struct work_struct mux_set_work;
+ enum atcphy_mode target_mode;
+};
+
+#endif
diff --git a/drivers/phy/apple/trace.c b/drivers/phy/apple/trace.c
new file mode 100644
index 0000000..a82dc08
--- /dev/null
+++ b/drivers/phy/apple/trace.c
@@ -0,0 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0
+#define CREATE_TRACE_POINTS
+#include "trace.h"
+
diff --git a/drivers/phy/apple/trace.h b/drivers/phy/apple/trace.h
new file mode 100644
index 0000000..c4c21c8
--- /dev/null
+++ b/drivers/phy/apple/trace.h
@@ -0,0 +1,147 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+/*
+ * Apple Type-C PHY driver
+ *
+ * Copyright (C) The Asahi Linux Contributors
+ * Author: Sven Peter <sven@svenpeter.dev>
+ */
+
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM appletypecphy
+
+#if !defined(_APPLETYPECPHY_TRACE_H_) || defined(TRACE_HEADER_MULTI_READ)
+#define _APPLETYPECPHY_TRACE_H_
+
+#include <linux/stringify.h>
+#include <linux/types.h>
+#include <linux/tracepoint.h>
+#include "atc.h"
+
+#define show_dp_lr(lr) \
+ __print_symbolic(lr, { ATCPHY_DP_LINK_RATE_RBR, "RBR" }, \
+ { ATCPHY_DP_LINK_RATE_HBR, "HBR" }, \
+ { ATCPHY_DP_LINK_RATE_HBR2, "HBR2" }, \
+ { ATCPHY_DP_LINK_RATE_HBR3, "HBR3" })
+
+#define show_sw_orientation(orientation) \
+ __print_symbolic(orientation, { TYPEC_ORIENTATION_NONE, "none" }, \
+ { TYPEC_ORIENTATION_NORMAL, "normal" }, \
+ { TYPEC_ORIENTATION_REVERSE, "reverse" })
+
+TRACE_EVENT(atcphy_sw_set, TP_PROTO(enum typec_orientation orientation),
+ TP_ARGS(orientation),
+
+ TP_STRUCT__entry(__field(enum typec_orientation, orientation)),
+
+ TP_fast_assign(__entry->orientation = orientation;),
+
+ TP_printk("orientation: %s",
+ show_sw_orientation(__entry->orientation)));
+
+#define show_mux_state(state) \
+ __print_symbolic(state.mode, { TYPEC_STATE_SAFE, "USB Safe State" }, \
+ { TYPEC_STATE_USB, "USB" })
+
+#define show_atcphy_mode(mode) \
+ __print_symbolic(mode, { APPLE_ATCPHY_MODE_OFF, "off" }, \
+ { APPLE_ATCPHY_MODE_USB2, "USB2" }, \
+ { APPLE_ATCPHY_MODE_USB3, "USB3" }, \
+ { APPLE_ATCPHY_MODE_USB3_DP, "DP + USB" }, \
+ { APPLE_ATCPHY_MODE_USB4, "USB4" }, \
+ { APPLE_ATCPHY_MODE_DP, "DP-only" })
+
+TRACE_EVENT(atcphy_usb3_set_mode,
+ TP_PROTO(struct apple_atcphy *atcphy, enum phy_mode mode,
+ int submode),
+ TP_ARGS(atcphy, mode, submode),
+
+ TP_STRUCT__entry(__field(enum atcphy_mode, mode)
+ __field(enum phy_mode, phy_mode)
+ __field(int, submode)),
+
+ TP_fast_assign(__entry->mode = atcphy->mode;
+ __entry->phy_mode = mode;
+ __entry->submode = submode;),
+
+ TP_printk("mode: %s, phy_mode: %d, submode: %d",
+ show_atcphy_mode(__entry->mode), __entry->phy_mode,
+ __entry->submode));
+
+TRACE_EVENT(
+ atcphy_configure_lanes,
+ TP_PROTO(enum atcphy_mode mode,
+ const struct atcphy_mode_configuration *cfg),
+ TP_ARGS(mode, cfg),
+
+ TP_STRUCT__entry(__field(enum atcphy_mode, mode) __field_struct(
+ struct atcphy_mode_configuration, cfg)),
+
+ TP_fast_assign(__entry->mode = mode; __entry->cfg = *cfg;),
+
+ TP_printk(
+ "mode: %s, crossbar: 0x%02x, lanes: {0x%02x, 0x%02x}, swap: %d",
+ show_atcphy_mode(__entry->mode), __entry->cfg.crossbar,
+ __entry->cfg.lane_mode[0], __entry->cfg.lane_mode[1],
+ __entry->cfg.set_swap));
+
+TRACE_EVENT(atcphy_mux_set, TP_PROTO(struct typec_mux_state *state),
+ TP_ARGS(state),
+
+ TP_STRUCT__entry(__field_struct(struct typec_mux_state, state)),
+
+ TP_fast_assign(__entry->state = *state;),
+
+ TP_printk("state: %s", show_mux_state(__entry->state)));
+
+TRACE_EVENT(atcphy_parsed_tunable,
+ TP_PROTO(const char *name, struct atcphy_tunable *tunable),
+ TP_ARGS(name, tunable),
+
+ TP_STRUCT__entry(__field(const char *, name)
+ __field(size_t, sz)),
+
+ TP_fast_assign(__entry->name = name; __entry->sz = tunable->sz;),
+
+ TP_printk("%s with %zu entries", __entry->name,
+ __entry->sz));
+
+TRACE_EVENT(
+ atcphy_fuses, TP_PROTO(struct apple_atcphy *atcphy), TP_ARGS(atcphy),
+ TP_STRUCT__entry(__field(struct apple_atcphy *, atcphy)),
+ TP_fast_assign(__entry->atcphy = atcphy;),
+ TP_printk(
+ "aus_cmn_shm_vreg_trim: 0x%02x; auspll_rodco_encap: 0x%02x; auspll_rodco_bias_adjust: 0x%02x; auspll_fracn_dll_start_capcode: 0x%02x; auspll_dtc_vreg_adjust: 0x%02x; cio3pll_dco_coarsebin: 0x%02x, 0x%02x; cio3pll_dll_start_capcode: 0x%02x, 0x%02x; cio3pll_dtc_vreg_adjust: 0x%02x",
+ __entry->atcphy->fuses.aus_cmn_shm_vreg_trim,
+ __entry->atcphy->fuses.auspll_rodco_encap,
+ __entry->atcphy->fuses.auspll_rodco_bias_adjust,
+ __entry->atcphy->fuses.auspll_fracn_dll_start_capcode,
+ __entry->atcphy->fuses.auspll_dtc_vreg_adjust,
+ __entry->atcphy->fuses.cio3pll_dco_coarsebin[0],
+ __entry->atcphy->fuses.cio3pll_dco_coarsebin[1],
+ __entry->atcphy->fuses.cio3pll_dll_start_capcode[0],
+ __entry->atcphy->fuses.cio3pll_dll_start_capcode[1],
+ __entry->atcphy->fuses.cio3pll_dtc_vreg_adjust));
+
+
+
+TRACE_EVENT(atcphy_dp_configure,
+ TP_PROTO(struct apple_atcphy *atcphy, enum atcphy_dp_link_rate lr),
+ TP_ARGS(atcphy, lr),
+
+ TP_STRUCT__entry(__string(devname, dev_name(atcphy->dev))
+ __field(enum atcphy_dp_link_rate, lr)),
+
+ TP_fast_assign(__assign_str(devname, dev_name(atcphy->dev));
+ __entry->lr = lr;),
+
+ TP_printk("%s: link rate: %s", __get_str(devname),
+ show_dp_lr(__entry->lr)));
+
+#endif /* _APPLETYPECPHY_TRACE_H_ */
+
+/* This part must be outside protection */
+#undef TRACE_INCLUDE_FILE
+#define TRACE_INCLUDE_FILE trace
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH .
+#include <trace/define_trace.h>
diff --git a/drivers/platform/Kconfig b/drivers/platform/Kconfig
index 868b203..8bfd8a1 100644
--- a/drivers/platform/Kconfig
+++ b/drivers/platform/Kconfig
@@ -14,3 +14,5 @@
source "drivers/platform/surface/Kconfig"
source "drivers/platform/x86/Kconfig"
+
+source "drivers/platform/apple/Kconfig"
diff --git a/drivers/platform/Makefile b/drivers/platform/Makefile
index 4164017..d2baa4eb 100644
--- a/drivers/platform/Makefile
+++ b/drivers/platform/Makefile
@@ -11,3 +11,4 @@
obj-$(CONFIG_GOLDFISH) += goldfish/
obj-$(CONFIG_CHROME_PLATFORMS) += chrome/
obj-$(CONFIG_SURFACE_PLATFORMS) += surface/
+obj-$(CONFIG_APPLE_PLATFORMS) += apple/
diff --git a/drivers/platform/apple/Kconfig b/drivers/platform/apple/Kconfig
new file mode 100644
index 0000000..5bcadd3
--- /dev/null
+++ b/drivers/platform/apple/Kconfig
@@ -0,0 +1,49 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Apple Platform-Specific Drivers
+#
+
+menuconfig APPLE_PLATFORMS
+ bool "Apple Mac Platform-Specific Device Drivers"
+ default y
+ help
+ Say Y here to get to see options for platform-specific device drivers
+ for Apple devices. This option alone does not add any kernel code.
+
+ If you say N, all options in this submenu will be skipped and disabled.
+
+if APPLE_PLATFORMS
+
+config APPLE_SMC
+ tristate "Apple SMC Driver"
+ depends on ARCH_APPLE || (COMPILE_TEST && 64BIT)
+ default ARCH_APPLE
+ select MFD_CORE
+ help
+ Build support for the Apple System Management Controller present in
+ Apple Macs. This driver currently supports the SMC in Apple Silicon
+ Macs. For x86 Macs, see the applesmc driver (SENSORS_APPLESMC).
+
+ Say Y here if you have an Apple Silicon Mac.
+
+ To compile this driver as a module, choose M here: the module will
+ be called macsmc.
+
+if APPLE_SMC
+
+config APPLE_SMC_RTKIT
+ tristate "RTKit (Apple Silicon) backend"
+ depends on ARCH_APPLE || (COMPILE_TEST && 64BIT)
+ depends on APPLE_RTKIT
+ default ARCH_APPLE
+ help
+ Build support for SMC communications via the RTKit backend. This is
+ required for Apple Silicon Macs.
+
+ Say Y here if you have an Apple Silicon Mac.
+
+ To compile this driver as a module, choose M here: the module will
+ be called macsmc-rtkit.
+
+endif
+endif
diff --git a/drivers/platform/apple/Makefile b/drivers/platform/apple/Makefile
new file mode 100644
index 0000000..79fac19
--- /dev/null
+++ b/drivers/platform/apple/Makefile
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for linux/drivers/platform/apple
+# Apple Platform-Specific Drivers
+#
+
+macsmc-y += smc_core.o
+macsmc-rtkit-y += smc_rtkit.o
+
+obj-$(CONFIG_APPLE_SMC) += macsmc.o
+obj-$(CONFIG_APPLE_SMC_RTKIT) += macsmc-rtkit.o
diff --git a/drivers/platform/apple/smc.h b/drivers/platform/apple/smc.h
new file mode 100644
index 0000000..34131f7
--- /dev/null
+++ b/drivers/platform/apple/smc.h
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple SMC internal core definitions
+ * Copyright (C) The Asahi Linux Contributors
+ */
+
+#ifndef _SMC_H
+#define _SMC_H
+
+#include <linux/mfd/macsmc.h>
+
+struct apple_smc_backend_ops {
+ int (*read_key)(void *cookie, smc_key key, void *buf, size_t size);
+ int (*write_key)(void *cookie, smc_key key, void *buf, size_t size);
+ int (*write_key_atomic)(void *cookie, smc_key key, void *buf, size_t size);
+ int (*rw_key)(void *cookie, smc_key key, void *wbuf, size_t wsize,
+ void *rbuf, size_t rsize);
+ int (*get_key_by_index)(void *cookie, int index, smc_key *key);
+ int (*get_key_info)(void *cookie, smc_key key, struct apple_smc_key_info *info);
+};
+
+int apple_smc_probe(struct device *dev, const struct apple_smc_backend_ops *ops, void *cookie);
+void *apple_smc_get_cookie(struct apple_smc *smc);
+int apple_smc_remove(struct apple_smc *smc);
+void apple_smc_event_received(struct apple_smc *smc, uint32_t event);
+
+#endif
diff --git a/drivers/platform/apple/smc_core.c b/drivers/platform/apple/smc_core.c
new file mode 100644
index 0000000..ee1df85
--- /dev/null
+++ b/drivers/platform/apple/smc_core.c
@@ -0,0 +1,297 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple SMC core framework
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include <linux/bitfield.h>
+#include <linux/device.h>
+#include <linux/mfd/core.h>
+#include <linux/mutex.h>
+#include <linux/notifier.h>
+#include "smc.h"
+
+struct apple_smc {
+ struct device *dev;
+
+ void *be_cookie;
+ const struct apple_smc_backend_ops *be;
+
+ struct mutex mutex;
+
+ u32 key_count;
+ smc_key first_key;
+ smc_key last_key;
+
+ struct blocking_notifier_head event_handlers;
+};
+
+static const struct mfd_cell apple_smc_devs[] = {
+ {
+ .name = "macsmc-gpio",
+ },
+ {
+ .name = "macsmc-hid",
+ },
+ {
+ .name = "macsmc-power",
+ },
+ {
+ .name = "macsmc-reboot",
+ },
+ {
+ .name = "macsmc-rtc",
+ },
+};
+
+int apple_smc_read(struct apple_smc *smc, smc_key key, void *buf, size_t size)
+{
+ int ret;
+
+ mutex_lock(&smc->mutex);
+ ret = smc->be->read_key(smc->be_cookie, key, buf, size);
+ mutex_unlock(&smc->mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL(apple_smc_read);
+
+int apple_smc_write(struct apple_smc *smc, smc_key key, void *buf, size_t size)
+{
+ int ret;
+
+ mutex_lock(&smc->mutex);
+ ret = smc->be->write_key(smc->be_cookie, key, buf, size);
+ mutex_unlock(&smc->mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL(apple_smc_write);
+
+int apple_smc_write_atomic(struct apple_smc *smc, smc_key key, void *buf, size_t size)
+{
+ int ret;
+
+ /*
+ * Will fail if SMC is busy. This is only used by SMC reboot/poweroff
+ * final calls, so it doesn't really matter at that point.
+ */
+ if (!mutex_trylock(&smc->mutex))
+ return -EBUSY;
+
+ ret = smc->be->write_key_atomic(smc->be_cookie, key, buf, size);
+ mutex_unlock(&smc->mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL(apple_smc_write_atomic);
+
+int apple_smc_rw(struct apple_smc *smc, smc_key key, void *wbuf, size_t wsize,
+ void *rbuf, size_t rsize)
+{
+ int ret;
+
+ mutex_lock(&smc->mutex);
+ ret = smc->be->rw_key(smc->be_cookie, key, wbuf, wsize, rbuf, rsize);
+ mutex_unlock(&smc->mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL(apple_smc_rw);
+
+int apple_smc_read_f32_scaled(struct apple_smc *smc, smc_key key, int *p, int scale)
+{
+ u32 fval;
+ u64 val;
+ int ret, exp;
+
+ ret = apple_smc_read_u32(smc, key, &fval);
+ if (ret < 0)
+ return ret;
+
+ val = ((u64)((fval & GENMASK(22, 0)) | BIT(23)));
+ exp = ((fval >> 23) & 0xff) - 127 - 23;
+ if (scale < 0) {
+ val <<= 32;
+ exp -= 32;
+ val /= -scale;
+ } else {
+ val *= scale;
+ }
+
+ if (exp > 63)
+ val = U64_MAX;
+ else if (exp < -63)
+ val = 0;
+ else if (exp < 0)
+ val >>= -exp;
+ else if (exp != 0 && (val & ~((1UL << (64 - exp)) - 1))) /* overflow */
+ val = U64_MAX;
+ else
+ val <<= exp;
+
+ if (fval & BIT(31)) {
+ if (val > (-(s64)INT_MIN))
+ *p = INT_MIN;
+ else
+ *p = -val;
+ } else {
+ if (val > INT_MAX)
+ *p = INT_MAX;
+ else
+ *p = val;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL(apple_smc_read_f32_scaled);
+
+int apple_smc_get_key_by_index(struct apple_smc *smc, int index, smc_key *key)
+{
+ int ret;
+
+ mutex_lock(&smc->mutex);
+ ret = smc->be->get_key_by_index(smc->be_cookie, index, key);
+ mutex_unlock(&smc->mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL(apple_smc_get_key_by_index);
+
+int apple_smc_get_key_info(struct apple_smc *smc, smc_key key, struct apple_smc_key_info *info)
+{
+ int ret;
+
+ mutex_lock(&smc->mutex);
+ ret = smc->be->get_key_info(smc->be_cookie, key, info);
+ mutex_unlock(&smc->mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL(apple_smc_get_key_info);
+
+int apple_smc_find_first_key_index(struct apple_smc *smc, smc_key key)
+{
+ int start = 0, count = smc->key_count;
+ int ret;
+
+ if (key <= smc->first_key)
+ return 0;
+ if (key > smc->last_key)
+ return smc->key_count;
+
+ while (count > 1) {
+ int pivot = start + ((count - 1) >> 1);
+ smc_key pkey;
+
+ ret = apple_smc_get_key_by_index(smc, pivot, &pkey);
+ if (ret < 0)
+ return ret;
+
+ if (pkey == key)
+ return pivot;
+
+ pivot++;
+
+ if (pkey < key) {
+ count -= pivot - start;
+ start = pivot;
+ } else {
+ count = pivot - start;
+ }
+ }
+
+ return start;
+}
+EXPORT_SYMBOL(apple_smc_find_first_key_index);
+
+int apple_smc_get_key_count(struct apple_smc *smc)
+{
+ return smc->key_count;
+}
+EXPORT_SYMBOL(apple_smc_get_key_count);
+
+void apple_smc_event_received(struct apple_smc *smc, uint32_t event)
+{
+ dev_dbg(smc->dev, "Event: 0x%08x\n", event);
+ blocking_notifier_call_chain(&smc->event_handlers, event, NULL);
+}
+EXPORT_SYMBOL(apple_smc_event_received);
+
+int apple_smc_register_notifier(struct apple_smc *smc, struct notifier_block *n)
+{
+ return blocking_notifier_chain_register(&smc->event_handlers, n);
+}
+EXPORT_SYMBOL(apple_smc_register_notifier);
+
+int apple_smc_unregister_notifier(struct apple_smc *smc, struct notifier_block *n)
+{
+ return blocking_notifier_chain_unregister(&smc->event_handlers, n);
+}
+EXPORT_SYMBOL(apple_smc_unregister_notifier);
+
+void *apple_smc_get_cookie(struct apple_smc *smc)
+{
+ return smc->be_cookie;
+}
+EXPORT_SYMBOL(apple_smc_get_cookie);
+
+int apple_smc_probe(struct device *dev, const struct apple_smc_backend_ops *ops, void *cookie)
+{
+ struct apple_smc *smc;
+ u32 count;
+ int ret;
+
+ smc = devm_kzalloc(dev, sizeof(*smc), GFP_KERNEL);
+ if (!smc)
+ return -ENOMEM;
+
+ smc->dev = dev;
+ smc->be_cookie = cookie;
+ smc->be = ops;
+ mutex_init(&smc->mutex);
+ BLOCKING_INIT_NOTIFIER_HEAD(&smc->event_handlers);
+
+ ret = apple_smc_read_u32(smc, SMC_KEY(#KEY), &count);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get key count");
+ smc->key_count = be32_to_cpu(count);
+
+ ret = apple_smc_get_key_by_index(smc, 0, &smc->first_key);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get first key");
+
+ ret = apple_smc_get_key_by_index(smc, smc->key_count - 1, &smc->last_key);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get last key");
+
+ dev_set_drvdata(dev, smc);
+
+ /* Enable notifications */
+ apple_smc_write_flag(smc, SMC_KEY(NTAP), 1);
+
+ dev_info(dev, "Initialized (%d keys %p4ch..%p4ch)\n",
+ smc->key_count, &smc->first_key, &smc->last_key);
+
+ ret = mfd_add_devices(dev, -1, apple_smc_devs, ARRAY_SIZE(apple_smc_devs), NULL, 0, NULL);
+ if (ret)
+ return dev_err_probe(dev, ret, "Subdevice initialization failed");
+
+ return 0;
+}
+EXPORT_SYMBOL(apple_smc_probe);
+
+int apple_smc_remove(struct apple_smc *smc)
+{
+ mfd_remove_devices(smc->dev);
+
+ /* Disable notifications */
+ apple_smc_write_flag(smc, SMC_KEY(NTAP), 1);
+
+ return 0;
+}
+EXPORT_SYMBOL(apple_smc_remove);
+
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_DESCRIPTION("Apple SMC core");
diff --git a/drivers/platform/apple/smc_rtkit.c b/drivers/platform/apple/smc_rtkit.c
new file mode 100644
index 0000000..77ad53f
--- /dev/null
+++ b/drivers/platform/apple/smc_rtkit.c
@@ -0,0 +1,451 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple SMC RTKit backend
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include <asm/unaligned.h>
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/soc/apple/rtkit.h>
+#include "smc.h"
+
+#define SMC_ENDPOINT 0x20
+
+/* Guess */
+#define SMC_SHMEM_SIZE 0x1000
+
+#define SMC_MSG_READ_KEY 0x10
+#define SMC_MSG_WRITE_KEY 0x11
+#define SMC_MSG_GET_KEY_BY_INDEX 0x12
+#define SMC_MSG_GET_KEY_INFO 0x13
+#define SMC_MSG_INITIALIZE 0x17
+#define SMC_MSG_NOTIFICATION 0x18
+#define SMC_MSG_RW_KEY 0x20
+
+#define SMC_DATA GENMASK(63, 32)
+#define SMC_WSIZE GENMASK(31, 24)
+#define SMC_SIZE GENMASK(23, 16)
+#define SMC_ID GENMASK(15, 12)
+#define SMC_MSG GENMASK(7, 0)
+#define SMC_RESULT SMC_MSG
+
+#define SMC_RECV_TIMEOUT 500
+
+struct apple_smc_rtkit {
+ struct device *dev;
+ struct apple_rtkit *rtk;
+
+ struct completion init_done;
+ bool initialized;
+ bool alive;
+
+ struct resource *sram;
+ void __iomem *sram_base;
+ struct apple_rtkit_shmem shmem;
+
+ unsigned int msg_id;
+
+ bool atomic_pending;
+ struct completion cmd_done;
+ u64 cmd_ret;
+};
+
+static int apple_smc_rtkit_write_key_atomic(void *cookie, smc_key key, void *buf, size_t size)
+{
+ struct apple_smc_rtkit *smc = cookie;
+ int ret;
+ u64 msg;
+ u8 result;
+
+ if (size > SMC_SHMEM_SIZE || size == 0)
+ return -EINVAL;
+
+ if (!smc->alive)
+ return -EIO;
+
+ memcpy_toio(smc->shmem.iomem, buf, size);
+ smc->msg_id = (smc->msg_id + 1) & 0xf;
+ msg = (FIELD_PREP(SMC_MSG, SMC_MSG_WRITE_KEY) |
+ FIELD_PREP(SMC_SIZE, size) |
+ FIELD_PREP(SMC_ID, smc->msg_id) |
+ FIELD_PREP(SMC_DATA, key));
+ smc->atomic_pending = true;
+
+ ret = apple_rtkit_send_message(smc->rtk, SMC_ENDPOINT, msg, NULL, true);
+ if (ret < 0) {
+ dev_err(smc->dev, "Failed to send command (%d)\n", ret);
+ return ret;
+ }
+
+ while (smc->atomic_pending) {
+ ret = apple_rtkit_poll(smc->rtk);
+ if (ret < 0) {
+ dev_err(smc->dev, "RTKit poll failed (%llx)", msg);
+ return ret;
+ }
+ udelay(100);
+ }
+
+ if (FIELD_GET(SMC_ID, smc->cmd_ret) != smc->msg_id) {
+ dev_err(smc->dev, "Command sequence mismatch (expected %d, got %d)\n",
+ smc->msg_id, (unsigned int)FIELD_GET(SMC_ID, smc->cmd_ret));
+ return -EIO;
+ }
+
+ result = FIELD_GET(SMC_RESULT, smc->cmd_ret);
+ if (result != 0)
+ return -result;
+
+ return FIELD_GET(SMC_SIZE, smc->cmd_ret);
+}
+
+static int apple_smc_cmd(struct apple_smc_rtkit *smc, u64 cmd, u64 arg,
+ u64 size, u64 wsize, u32 *ret_data)
+{
+ int ret;
+ u64 msg;
+ u8 result;
+
+ if (!smc->alive)
+ return -EIO;
+
+ reinit_completion(&smc->cmd_done);
+
+ smc->msg_id = (smc->msg_id + 1) & 0xf;
+ msg = (FIELD_PREP(SMC_MSG, cmd) |
+ FIELD_PREP(SMC_SIZE, size) |
+ FIELD_PREP(SMC_WSIZE, wsize) |
+ FIELD_PREP(SMC_ID, smc->msg_id) |
+ FIELD_PREP(SMC_DATA, arg));
+
+ ret = apple_rtkit_send_message(smc->rtk, SMC_ENDPOINT, msg, NULL, false);
+ if (ret < 0) {
+ dev_err(smc->dev, "Failed to send command\n");
+ return ret;
+ }
+
+ do {
+ if (wait_for_completion_timeout(&smc->cmd_done,
+ msecs_to_jiffies(SMC_RECV_TIMEOUT)) == 0) {
+ dev_err(smc->dev, "Command timed out (%llx)", msg);
+ return -ETIMEDOUT;
+ }
+ if (FIELD_GET(SMC_ID, smc->cmd_ret) == smc->msg_id)
+ break;
+ dev_err(smc->dev, "Command sequence mismatch (expected %d, got %d)\n",
+ smc->msg_id, (unsigned int)FIELD_GET(SMC_ID, smc->cmd_ret));
+ } while(1);
+
+ result = FIELD_GET(SMC_RESULT, smc->cmd_ret);
+ if (result != 0)
+ return -result;
+
+ if (ret_data)
+ *ret_data = FIELD_GET(SMC_DATA, smc->cmd_ret);
+
+ return FIELD_GET(SMC_SIZE, smc->cmd_ret);
+}
+
+static int _apple_smc_rtkit_read_key(struct apple_smc_rtkit *smc, smc_key key,
+ void *buf, size_t size, size_t wsize)
+{
+ int ret;
+ u32 rdata;
+ u64 cmd;
+
+ if (size > SMC_SHMEM_SIZE || size == 0)
+ return -EINVAL;
+
+ cmd = wsize ? SMC_MSG_RW_KEY : SMC_MSG_READ_KEY;
+
+ ret = apple_smc_cmd(smc, cmd, key, size, wsize, &rdata);
+ if (ret < 0)
+ return ret;
+
+ if (size <= 4)
+ memcpy(buf, &rdata, size);
+ else
+ memcpy_fromio(buf, smc->shmem.iomem, size);
+
+ return ret;
+}
+
+static int apple_smc_rtkit_read_key(void *cookie, smc_key key, void *buf, size_t size)
+{
+ return _apple_smc_rtkit_read_key(cookie, key, buf, size, 0);
+}
+
+static int apple_smc_rtkit_write_key(void *cookie, smc_key key, void *buf, size_t size)
+{
+ struct apple_smc_rtkit *smc = cookie;
+
+ if (size > SMC_SHMEM_SIZE || size == 0)
+ return -EINVAL;
+
+ memcpy_toio(smc->shmem.iomem, buf, size);
+ return apple_smc_cmd(smc, SMC_MSG_WRITE_KEY, key, size, 0, NULL);
+}
+
+static int apple_smc_rtkit_rw_key(void *cookie, smc_key key,
+ void *wbuf, size_t wsize, void *rbuf, size_t rsize)
+{
+ struct apple_smc_rtkit *smc = cookie;
+
+ if (wsize > SMC_SHMEM_SIZE || wsize == 0)
+ return -EINVAL;
+
+ memcpy_toio(smc->shmem.iomem, wbuf, wsize);
+ return _apple_smc_rtkit_read_key(smc, key, rbuf, rsize, wsize);
+}
+
+static int apple_smc_rtkit_get_key_by_index(void *cookie, int index, smc_key *key)
+{
+ struct apple_smc_rtkit *smc = cookie;
+ int ret;
+
+ ret = apple_smc_cmd(smc, SMC_MSG_GET_KEY_BY_INDEX, index, 0, 0, key);
+
+ *key = swab32(*key);
+ return ret;
+}
+
+static int apple_smc_rtkit_get_key_info(void *cookie, smc_key key, struct apple_smc_key_info *info)
+{
+ struct apple_smc_rtkit *smc = cookie;
+ u8 key_info[6];
+ int ret;
+
+ ret = apple_smc_cmd(smc, SMC_MSG_GET_KEY_INFO, key, 0, 0, NULL);
+ if (ret >= 0 && info) {
+ memcpy_fromio(key_info, smc->shmem.iomem, sizeof(key_info));
+ info->size = key_info[0];
+ info->type_code = get_unaligned_be32(&key_info[1]);
+ info->flags = key_info[5];
+ }
+ return ret;
+}
+
+static const struct apple_smc_backend_ops apple_smc_rtkit_be_ops = {
+ .read_key = apple_smc_rtkit_read_key,
+ .write_key = apple_smc_rtkit_write_key,
+ .write_key_atomic = apple_smc_rtkit_write_key_atomic,
+ .rw_key = apple_smc_rtkit_rw_key,
+ .get_key_by_index = apple_smc_rtkit_get_key_by_index,
+ .get_key_info = apple_smc_rtkit_get_key_info,
+};
+
+static void apple_smc_rtkit_crashed(void *cookie)
+{
+ struct apple_smc_rtkit *smc = cookie;
+
+ dev_err(smc->dev, "SMC crashed! Your system will reboot in a few seconds...\n");
+ smc->alive = false;
+}
+
+static int apple_smc_rtkit_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr)
+{
+ struct apple_smc_rtkit *smc = cookie;
+ struct resource res = {
+ .start = bfr->iova,
+ .end = bfr->iova + bfr->size - 1,
+ .name = "rtkit_map",
+ .flags = smc->sram->flags,
+ };
+
+ if (!bfr->iova) {
+ dev_err(smc->dev, "RTKit wants a RAM buffer\n");
+ return -EIO;
+ }
+
+ if (res.end < res.start || !resource_contains(smc->sram, &res)) {
+ dev_err(smc->dev,
+ "RTKit buffer request outside SRAM region: %pR", &res);
+ return -EFAULT;
+ }
+
+ bfr->iomem = smc->sram_base + (res.start - smc->sram->start);
+ bfr->is_mapped = true;
+
+ return 0;
+}
+
+static void apple_smc_rtkit_shmem_destroy(void *cookie, struct apple_rtkit_shmem *bfr)
+{
+ // no-op
+}
+
+static bool apple_smc_rtkit_recv_early(void *cookie, u8 endpoint, u64 message)
+{
+ struct apple_smc_rtkit *smc = cookie;
+
+ if (endpoint != SMC_ENDPOINT) {
+ dev_err(smc->dev, "Received message for unknown endpoint 0x%x\n", endpoint);
+ return false;
+ }
+
+ if (!smc->initialized) {
+ int ret;
+
+ smc->shmem.iova = message;
+ smc->shmem.size = SMC_SHMEM_SIZE;
+ ret = apple_smc_rtkit_shmem_setup(smc, &smc->shmem);
+ if (ret < 0)
+ dev_err(smc->dev, "Failed to initialize shared memory\n");
+ else
+ smc->alive = true;
+ smc->initialized = true;
+ complete(&smc->init_done);
+ } else if (FIELD_GET(SMC_MSG, message) == SMC_MSG_NOTIFICATION) {
+ /* Handle these in the RTKit worker thread */
+ return false;
+ } else {
+ smc->cmd_ret = message;
+ if (smc->atomic_pending) {
+ smc->atomic_pending = false;
+ } else {
+ complete(&smc->cmd_done);
+ }
+ }
+
+ return true;
+}
+
+static void apple_smc_rtkit_recv(void *cookie, u8 endpoint, u64 message)
+{
+ struct apple_smc_rtkit *smc = cookie;
+ struct apple_smc *core = dev_get_drvdata(smc->dev);
+
+ if (endpoint != SMC_ENDPOINT) {
+ dev_err(smc->dev, "Received message for unknown endpoint 0x%x\n", endpoint);
+ return;
+ }
+
+ if (FIELD_GET(SMC_MSG, message) != SMC_MSG_NOTIFICATION) {
+ dev_err(smc->dev, "Received unknown message from worker: 0x%llx\n", message);
+ return;
+ }
+
+ apple_smc_event_received(core, FIELD_GET(SMC_DATA, message));
+}
+
+static const struct apple_rtkit_ops apple_smc_rtkit_ops = {
+ .crashed = apple_smc_rtkit_crashed,
+ .recv_message = apple_smc_rtkit_recv,
+ .recv_message_early = apple_smc_rtkit_recv_early,
+ .shmem_setup = apple_smc_rtkit_shmem_setup,
+ .shmem_destroy = apple_smc_rtkit_shmem_destroy,
+};
+
+static int apple_smc_rtkit_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct apple_smc_rtkit *smc;
+ int ret;
+
+ smc = devm_kzalloc(dev, sizeof(*smc), GFP_KERNEL);
+ if (!smc)
+ return -ENOMEM;
+
+ smc->dev = dev;
+
+ smc->sram = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sram");
+ if (!smc->sram)
+ return dev_err_probe(dev, EIO,
+ "No SRAM region");
+
+ smc->sram_base = devm_ioremap_resource(dev, smc->sram);
+ if (IS_ERR(smc->sram_base))
+ return dev_err_probe(dev, PTR_ERR(smc->sram_base),
+ "Failed to map SRAM region");
+
+ smc->rtk =
+ devm_apple_rtkit_init(dev, smc, NULL, 0, &apple_smc_rtkit_ops);
+ if (IS_ERR(smc->rtk))
+ return dev_err_probe(dev, PTR_ERR(smc->rtk),
+ "Failed to intialize RTKit");
+
+ ret = apple_rtkit_wake(smc->rtk);
+ if (ret != 0)
+ return dev_err_probe(dev, ret,
+ "Failed to wake up SMC");
+
+ ret = apple_rtkit_start_ep(smc->rtk, SMC_ENDPOINT);
+ if (ret != 0) {
+ dev_err(dev, "Failed to start endpoint");
+ goto cleanup;
+ }
+
+ init_completion(&smc->init_done);
+ init_completion(&smc->cmd_done);
+
+ ret = apple_rtkit_send_message(smc->rtk, SMC_ENDPOINT,
+ FIELD_PREP(SMC_MSG, SMC_MSG_INITIALIZE), NULL, false);
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "Failed to send init message");
+
+ if (wait_for_completion_timeout(&smc->init_done,
+ msecs_to_jiffies(SMC_RECV_TIMEOUT)) == 0) {
+ ret = -ETIMEDOUT;
+ dev_err(dev, "Timed out initializing SMC");
+ goto cleanup;
+ }
+
+ if (!smc->alive) {
+ ret = -EIO;
+ goto cleanup;
+ }
+
+ ret = apple_smc_probe(dev, &apple_smc_rtkit_be_ops, smc);
+ if (ret)
+ goto cleanup;
+
+ return 0;
+
+cleanup:
+ /* Try to shut down RTKit, if it's not completely wedged */
+ if (apple_rtkit_is_running(smc->rtk))
+ apple_rtkit_quiesce(smc->rtk);
+
+ return ret;
+}
+
+static int apple_smc_rtkit_remove(struct platform_device *pdev)
+{
+ struct apple_smc *core = platform_get_drvdata(pdev);
+ struct apple_smc_rtkit *smc = apple_smc_get_cookie(core);
+
+ apple_smc_remove(core);
+
+ if (apple_rtkit_is_running(smc->rtk))
+ apple_rtkit_quiesce(smc->rtk);
+
+ return 0;
+}
+
+static const struct of_device_id apple_smc_rtkit_of_match[] = {
+ { .compatible = "apple,smc" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, apple_smc_rtkit_of_match);
+
+static struct platform_driver apple_smc_rtkit_driver = {
+ .driver = {
+ .name = "macsmc-rtkit",
+ .owner = THIS_MODULE,
+ .of_match_table = apple_smc_rtkit_of_match,
+ },
+ .probe = apple_smc_rtkit_probe,
+ .remove = apple_smc_rtkit_remove,
+};
+module_platform_driver(apple_smc_rtkit_driver);
+
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_DESCRIPTION("Apple SMC RTKit backend driver");
diff --git a/drivers/pmdomain/apple/pmgr-pwrstate.c b/drivers/pmdomain/apple/pmgr-pwrstate.c
index d62a776..fdab263 100644
--- a/drivers/pmdomain/apple/pmgr-pwrstate.c
+++ b/drivers/pmdomain/apple/pmgr-pwrstate.c
@@ -242,6 +242,8 @@ static int apple_pmgr_ps_probe(struct platform_device *pdev)
/* Turn it on so pm_genpd_init does not fail */
active = apple_pmgr_ps_power_on(&ps->genpd) == 0;
}
+ } else if (active) {
+ ps->genpd.flags |= GENPD_FLAG_DEFER_OFF | GENPD_FLAG_ACTIVE_WAKEUP;
}
/* Turn on auto-PM if the domain is already on */
diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig
index fece990..d5c1d3f 100644
--- a/drivers/power/reset/Kconfig
+++ b/drivers/power/reset/Kconfig
@@ -117,6 +117,18 @@
Say Y here if you have a Buffalo LinkStation LS421D/E.
+config POWER_RESET_MACSMC
+ tristate "Apple SMC reset/power-off driver"
+ depends on ARCH_APPLE || COMPILE_TEST
+ depends on APPLE_SMC
+ depends on OF
+ default ARCH_APPLE
+ help
+ This driver supports reset and power-off on Apple Mac machines
+ that implement this functionality via the SMC.
+
+ Say Y here if you have an Apple Silicon Mac.
+
config POWER_RESET_MSM
bool "Qualcomm MSM power-off driver"
depends on ARCH_QCOM
diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile
index a95d1bd..615536c 100644
--- a/drivers/power/reset/Makefile
+++ b/drivers/power/reset/Makefile
@@ -12,6 +12,7 @@
obj-$(CONFIG_POWER_RESET_GPIO_RESTART) += gpio-restart.o
obj-$(CONFIG_POWER_RESET_HISI) += hisi-reboot.o
obj-$(CONFIG_POWER_RESET_LINKSTATION) += linkstation-poweroff.o
+obj-$(CONFIG_POWER_RESET_MACSMC) += macsmc-reboot.o
obj-$(CONFIG_POWER_RESET_MSM) += msm-poweroff.o
obj-$(CONFIG_POWER_RESET_MT6323) += mt6323-poweroff.o
obj-$(CONFIG_POWER_RESET_QCOM_PON) += qcom-pon.o
diff --git a/drivers/power/reset/macsmc-reboot.c b/drivers/power/reset/macsmc-reboot.c
new file mode 100644
index 0000000..c33ba2a
--- /dev/null
+++ b/drivers/power/reset/macsmc-reboot.c
@@ -0,0 +1,336 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple SMC Reboot/Poweroff Handler
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include <linux/delay.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/macsmc.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <linux/slab.h>
+
+struct macsmc_reboot_nvmem {
+ struct nvmem_cell *shutdown_flag;
+ struct nvmem_cell *pm_setting;
+ struct nvmem_cell *boot_stage;
+ struct nvmem_cell *boot_error_count;
+ struct nvmem_cell *panic_count;
+};
+
+static const char *nvmem_names[] = {
+ "shutdown_flag",
+ "pm_setting",
+ "boot_stage",
+ "boot_error_count",
+ "panic_count",
+};
+
+enum boot_stage {
+ BOOT_STAGE_SHUTDOWN = 0x00, /* Clean shutdown */
+ BOOT_STAGE_IBOOT_DONE = 0x2f, /* Last stage of bootloader */
+ BOOT_STAGE_KERNEL_STARTED = 0x30, /* Normal OS booting */
+};
+
+enum pm_setting {
+ PM_SETTING_AC_POWER_RESTORE = 0x02,
+ PM_SETTING_AC_POWER_OFF = 0x03,
+};
+
+static const char *ac_power_modes[] = { "off", "restore" };
+
+static int ac_power_mode_map[] = {
+ PM_SETTING_AC_POWER_OFF,
+ PM_SETTING_AC_POWER_RESTORE,
+};
+
+struct macsmc_reboot {
+ struct device *dev;
+ struct apple_smc *smc;
+ struct notifier_block reboot_notify;
+
+ union {
+ struct macsmc_reboot_nvmem nvm;
+ struct nvmem_cell *nvm_cells[ARRAY_SIZE(nvmem_names)];
+ };
+};
+
+/* Helpers to read/write a u8 given a struct nvmem_cell */
+static int nvmem_cell_get_u8(struct nvmem_cell *cell)
+{
+ size_t len;
+ u8 val;
+ void *ret = nvmem_cell_read(cell, &len);
+
+ if (IS_ERR(ret))
+ return PTR_ERR(ret);
+
+ if (len < 1) {
+ kfree(ret);
+ return -EINVAL;
+ }
+
+ val = *(u8 *)ret;
+ kfree(ret);
+ return val;
+}
+
+static int nvmem_cell_set_u8(struct nvmem_cell *cell, u8 val)
+{
+ return nvmem_cell_write(cell, &val, sizeof(val));
+}
+
+static ssize_t macsmc_ac_power_mode_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t n)
+{
+ struct macsmc_reboot *reboot = dev_get_drvdata(dev);
+ int mode;
+ int ret;
+
+ mode = sysfs_match_string(ac_power_modes, buf);
+ if (mode < 0)
+ return mode;
+
+ ret = nvmem_cell_set_u8(reboot->nvm.pm_setting, ac_power_mode_map[mode]);
+ if (ret < 0)
+ return ret;
+
+ return n;
+}
+
+static ssize_t macsmc_ac_power_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct macsmc_reboot *reboot = dev_get_drvdata(dev);
+ int len = 0;
+ int i;
+ int mode = nvmem_cell_get_u8(reboot->nvm.pm_setting);
+
+ if (mode < 0)
+ return mode;
+
+ for (i = 0; i < ARRAY_SIZE(ac_power_mode_map); i++)
+ if (mode == ac_power_mode_map[i])
+ len += scnprintf(buf+len, PAGE_SIZE-len,
+ "[%s] ", ac_power_modes[i]);
+ else
+ len += scnprintf(buf+len, PAGE_SIZE-len,
+ "%s ", ac_power_modes[i]);
+ buf[len-1] = '\n';
+ return len;
+}
+static DEVICE_ATTR(ac_power_mode, 0644, macsmc_ac_power_mode_show,
+ macsmc_ac_power_mode_store);
+
+/*
+ * SMC 'MBSE' key actions:
+ *
+ * 'offw' - shutdown warning
+ * 'slpw' - sleep warning
+ * 'rest' - restart warning
+ * 'off1' - shutdown (needs PMU bit set to stay on)
+ * 'susp' - suspend
+ * 'phra' - restart ("PE Halt Restart Action"?)
+ * 'panb' - panic beginning
+ * 'pane' - panic end
+ */
+
+static int macsmc_power_off(struct sys_off_data *data)
+{
+ struct macsmc_reboot *reboot = data->cb_data;
+
+ dev_info(reboot->dev, "Issuing power off (off1)\n");
+
+ if (apple_smc_write_u32_atomic(reboot->smc, SMC_KEY(MBSE), SMC_KEY(off1)) < 0) {
+ dev_err(reboot->dev, "Failed to issue MBSE = off1 (power_off)\n");
+ } else {
+ mdelay(100);
+ WARN_ON(1);
+ }
+
+ return NOTIFY_OK;
+}
+
+static int macsmc_restart(struct sys_off_data *data)
+{
+ struct macsmc_reboot *reboot = data->cb_data;
+
+ dev_info(reboot->dev, "Issuing restart (phra)\n");
+
+ if (apple_smc_write_u32_atomic(reboot->smc, SMC_KEY(MBSE), SMC_KEY(phra)) < 0) {
+ dev_err(reboot->dev, "Failed to issue MBSE = phra (restart)\n");
+ } else {
+ mdelay(100);
+ WARN_ON(1);
+ }
+
+ return NOTIFY_OK;
+}
+
+static int macsmc_reboot_notify(struct notifier_block *this, unsigned long action, void *data)
+{
+ struct macsmc_reboot *reboot = container_of(this, struct macsmc_reboot, reboot_notify);
+ u32 val;
+ u8 shutdown_flag;
+
+ switch (action) {
+ case SYS_RESTART:
+ val = SMC_KEY(rest);
+ shutdown_flag = 0;
+ break;
+ case SYS_POWER_OFF:
+ val = SMC_KEY(offw);
+ shutdown_flag = 1;
+ break;
+ default:
+ return NOTIFY_DONE;
+ }
+
+ dev_info(reboot->dev, "Preparing for reboot (%p4ch)\n", &val);
+
+ /* On the Mac Mini, this will turn off the LED for power off */
+ if (apple_smc_write_u32(reboot->smc, SMC_KEY(MBSE), val) < 0)
+ dev_err(reboot->dev, "Failed to issue MBSE = %p4ch (reboot_prepare)\n", &val);
+
+ /* Set the boot_stage to 0, which means we're doing a clean shutdown/reboot. */
+ if (reboot->nvm.boot_stage &&
+ nvmem_cell_set_u8(reboot->nvm.boot_stage, BOOT_STAGE_SHUTDOWN) < 0)
+ dev_err(reboot->dev, "Failed to write boot_stage\n");
+
+ /*
+ * Set the PMU flag to actually reboot into the off state.
+ * Without this, the device will just reboot. We make it optional in case it is no longer
+ * necessary on newer hardware.
+ */
+ if (reboot->nvm.shutdown_flag &&
+ nvmem_cell_set_u8(reboot->nvm.shutdown_flag, shutdown_flag) < 0)
+ dev_err(reboot->dev, "Failed to write shutdown_flag\n");
+
+ return NOTIFY_OK;
+}
+
+static void macsmc_power_init_error_counts(struct macsmc_reboot *reboot)
+{
+ int boot_error_count, panic_count;
+
+ if (!reboot->nvm.boot_error_count || !reboot->nvm.panic_count)
+ return;
+
+ boot_error_count = nvmem_cell_get_u8(reboot->nvm.boot_error_count);
+ if (boot_error_count < 0) {
+ dev_err(reboot->dev, "Failed to read boot_error_count (%d)\n", boot_error_count);
+ return;
+ }
+
+ panic_count = nvmem_cell_get_u8(reboot->nvm.panic_count);
+ if (panic_count < 0) {
+ dev_err(reboot->dev, "Failed to read panic_count (%d)\n", panic_count);
+ return;
+ }
+
+ if (!boot_error_count && !panic_count)
+ return;
+
+ dev_warn(reboot->dev, "PMU logged %d boot error(s) and %d panic(s)\n",
+ boot_error_count, panic_count);
+
+ if (nvmem_cell_set_u8(reboot->nvm.panic_count, 0) < 0)
+ dev_err(reboot->dev, "Failed to reset panic_count\n");
+ if (nvmem_cell_set_u8(reboot->nvm.boot_error_count, 0) < 0)
+ dev_err(reboot->dev, "Failed to reset boot_error_count\n");
+}
+
+static int macsmc_reboot_probe(struct platform_device *pdev)
+{
+ struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent);
+ struct macsmc_reboot *reboot;
+ int ret, i;
+
+ /* Ignore devices without this functionality */
+ if (!apple_smc_key_exists(smc, SMC_KEY(MBSE)))
+ return -ENODEV;
+
+ reboot = devm_kzalloc(&pdev->dev, sizeof(*reboot), GFP_KERNEL);
+ if (!reboot)
+ return -ENOMEM;
+
+ reboot->dev = &pdev->dev;
+ reboot->smc = smc;
+
+ platform_set_drvdata(pdev, reboot);
+
+ pdev->dev.of_node = of_get_child_by_name(pdev->dev.parent->of_node, "reboot");
+
+ for (i = 0; i < ARRAY_SIZE(nvmem_names); i++) {
+ struct nvmem_cell *cell;
+ cell = devm_nvmem_cell_get(&pdev->dev,
+ nvmem_names[i]);
+ if (IS_ERR(cell)) {
+ if (PTR_ERR(cell) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+ dev_warn(&pdev->dev, "Missing NVMEM cell %s (%ld)\n",
+ nvmem_names[i], PTR_ERR(cell));
+ /* Non fatal, we'll deal with it */
+ cell = NULL;
+ }
+ reboot->nvm_cells[i] = cell;
+ }
+
+ /* Set the boot_stage to indicate we're running the OS kernel */
+ if (reboot->nvm.boot_stage &&
+ nvmem_cell_set_u8(reboot->nvm.boot_stage, BOOT_STAGE_KERNEL_STARTED) < 0)
+ dev_err(reboot->dev, "Failed to write boot_stage\n");
+
+ /* Display and clear the error counts */
+ macsmc_power_init_error_counts(reboot);
+
+ reboot->reboot_notify.notifier_call = macsmc_reboot_notify;
+
+ ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_POWER_OFF, SYS_OFF_PRIO_HIGH,
+ macsmc_power_off, reboot);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "Failed to register power-off handler\n");
+
+ ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_RESTART, SYS_OFF_PRIO_HIGH,
+ macsmc_restart, reboot);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "Failed to register restart handler\n");
+
+ ret = devm_register_reboot_notifier(&pdev->dev, &reboot->reboot_notify);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "Failed to register reboot notifier\n");
+
+ dev_info(&pdev->dev, "Handling reboot and poweroff requests via SMC\n");
+
+ if (device_create_file(&pdev->dev, &dev_attr_ac_power_mode))
+ dev_warn(&pdev->dev, "could not create sysfs file\n");
+
+ return 0;
+}
+
+static int macsmc_reboot_remove(struct platform_device *pdev)
+{
+ device_remove_file(&pdev->dev, &dev_attr_ac_power_mode);
+
+ return 0;
+}
+
+
+static struct platform_driver macsmc_reboot_driver = {
+ .driver = {
+ .name = "macsmc-reboot",
+ .owner = THIS_MODULE,
+ },
+ .probe = macsmc_reboot_probe,
+ .remove = macsmc_reboot_remove,
+};
+module_platform_driver(macsmc_reboot_driver);
+
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_DESCRIPTION("Apple SMC reboot/poweroff driver");
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
+MODULE_ALIAS("platform:macsmc-reboot");
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index f21cb05..2f8d612 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -984,4 +984,11 @@
the state of charge, temperature, cycle count, actual and design
capacity, etc.
+config CHARGER_MACSMC
+ tristate "Apple SMC Charger / Battery support"
+ depends on APPLE_SMC
+ help
+ Say Y here to enable support for the charger and battery controls on
+ Apple SMC controllers, as used on Apple Silicon Macs.
+
endif # POWER_SUPPLY
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index 58b5672..ae44af9 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -114,3 +114,4 @@
obj-$(CONFIG_BATTERY_UG3105) += ug3105_battery.o
obj-$(CONFIG_CHARGER_QCOM_SMB2) += qcom_pmi8998_charger.o
obj-$(CONFIG_FUEL_GAUGE_MM8013) += mm8013.o
+obj-$(CONFIG_CHARGER_MACSMC) += macsmc_power.o
diff --git a/drivers/power/supply/macsmc_power.c b/drivers/power/supply/macsmc_power.c
new file mode 100644
index 0000000..5ca3f7c
--- /dev/null
+++ b/drivers/power/supply/macsmc_power.c
@@ -0,0 +1,787 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple SMC Power/Battery Management
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include <linux/ctype.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/macsmc.h>
+#include <linux/power_supply.h>
+#include <linux/reboot.h>
+#include <linux/delay.h>
+#include <linux/workqueue.h>
+
+#define MAX_STRING_LENGTH 256
+
+struct macsmc_power {
+ struct device *dev;
+ struct apple_smc *smc;
+ struct power_supply_desc batt_desc;
+
+ struct power_supply *batt;
+ char model_name[MAX_STRING_LENGTH];
+ char serial_number[MAX_STRING_LENGTH];
+ char mfg_date[MAX_STRING_LENGTH];
+ bool has_chwa;
+
+ struct power_supply *ac;
+
+ struct notifier_block nb;
+
+ struct work_struct critical_work;
+ bool shutdown_started;
+
+ struct delayed_work dbg_log_work;
+};
+
+static int macsmc_log_power_set(const char *val, const struct kernel_param *kp);
+
+static const struct kernel_param_ops macsmc_log_power_ops = {
+ .set = macsmc_log_power_set,
+ .get = param_get_bool,
+};
+
+static bool log_power = false;
+module_param_cb(log_power, &macsmc_log_power_ops, &log_power, 0644);
+MODULE_PARM_DESC(log_power, "Periodically log power consumption for debugging");
+
+#define POWER_LOG_INTERVAL (HZ)
+
+static struct macsmc_power *g_power;
+
+#define CHNC_BATTERY_FULL BIT(0)
+#define CHNC_NO_CHARGER BIT(7)
+#define CHNC_NOCHG_CH0C BIT(14)
+#define CHNC_NOCHG_CH0B_CH0K BIT(15)
+#define CHNC_BATTERY_FULL_2 BIT(18)
+#define CHNC_BMS_BUSY BIT(23)
+#define CHNC_NOAC_CH0J BIT(53)
+#define CHNC_NOAC_CH0I BIT(54)
+
+#define CH0R_LOWER_FLAGS GENMASK(15, 0)
+#define CH0R_NOAC_CH0I BIT(0)
+#define CH0R_NOAC_DISCONNECTED BIT(4)
+#define CH0R_NOAC_CH0J BIT(5)
+#define CH0R_BMS_BUSY BIT(8)
+#define CH0R_NOAC_CH0K BIT(9)
+#define CH0R_NOAC_CHWA BIT(11)
+
+#define CH0X_CH0C BIT(0)
+#define CH0X_CH0B BIT(1)
+
+#define ACSt_CAN_BOOT_AP BIT(2)
+#define ACSt_CAN_BOOT_IBOOT BIT(1)
+
+#define CHWA_FIXED_START_THRESHOLD 75
+#define CHWA_FIXED_END_THRESHOLD 80
+#define CHWA_PROP_WRITE_THRESHOLD 95
+
+static void macsmc_do_dbg(struct macsmc_power *power)
+{
+ int p_in = 0, p_sys = 0, p_3v8 = 0, p_mpmu = 0, p_spmu = 0, p_clvr = 0, p_cpu = 0;
+ s32 p_bat = 0;
+ s16 t_full = 0, t_empty = 0;
+ u8 charge = 0;
+
+ apple_smc_read_f32_scaled(power->smc, SMC_KEY(PDTR), &p_in, 1000);
+ apple_smc_read_f32_scaled(power->smc, SMC_KEY(PSTR), &p_sys, 1000);
+ apple_smc_read_f32_scaled(power->smc, SMC_KEY(PMVR), &p_3v8, 1000);
+ apple_smc_read_f32_scaled(power->smc, SMC_KEY(PHPC), &p_cpu, 1000);
+ apple_smc_read_f32_scaled(power->smc, SMC_KEY(PSVR), &p_clvr, 1000);
+ apple_smc_read_f32_scaled(power->smc, SMC_KEY(PPMC), &p_mpmu, 1000);
+ apple_smc_read_f32_scaled(power->smc, SMC_KEY(PPSC), &p_spmu, 1000);
+ apple_smc_read_s32(power->smc, SMC_KEY(B0AP), &p_bat);
+ apple_smc_read_s16(power->smc, SMC_KEY(B0TE), &t_empty);
+ apple_smc_read_s16(power->smc, SMC_KEY(B0TF), &t_full);
+ apple_smc_read_u8(power->smc, SMC_KEY(BUIC), &charge);
+
+#define FD3(x) ((x) / 1000), abs((x) % 1000)
+ dev_info(power->dev,
+ "In %2d.%03dW Sys %2d.%03dW 3V8 %2d.%03dW MPMU %2d.%03dW SPMU %2d.%03dW "
+ "CLVR %2d.%03dW CPU %2d.%03dW Batt %2d.%03dW %d%% T%s %dm\n",
+ FD3(p_in), FD3(p_sys), FD3(p_3v8), FD3(p_mpmu), FD3(p_spmu), FD3(p_clvr),
+ FD3(p_cpu), FD3(p_bat), charge,
+ t_full >= 0 ? "full" : "empty",
+ t_full >= 0 ? t_full : t_empty);
+#undef FD3
+}
+
+static int macsmc_battery_get_status(struct macsmc_power *power)
+{
+ u64 nocharge_flags;
+ u32 nopower_flags;
+ u16 ac_current;
+ bool chwa_limit = false;
+ int ret;
+
+ /*
+ * Note: there are fallbacks in case some of these SMC keys disappear in the future
+ * or are not present on some machines. We treat the absence of the CHCE/CHCC/BSFC/CHSC
+ * flags as an error, since they are quite fundamental and simple booleans.
+ */
+
+ /*
+ * If power input is inhibited, we are definitely discharging.
+ * However, if the only reason is the BMS is doing a balancing cycle,
+ * go ahead and ignore that one to avoid spooking users.
+ */
+ ret = apple_smc_read_u32(power->smc, SMC_KEY(CH0R), &nopower_flags);
+ if (!ret && (nopower_flags & CH0R_LOWER_FLAGS & ~CH0R_BMS_BUSY))
+ return POWER_SUPPLY_STATUS_DISCHARGING;
+
+ /* If no charger is present, we are definitely discharging. */
+ ret = apple_smc_read_flag(power->smc, SMC_KEY(CHCE));
+ if (ret < 0)
+ return ret;
+ else if (!ret)
+ return POWER_SUPPLY_STATUS_DISCHARGING;
+
+ /* If AC is not charge capable, we are definitely discharging. */
+ ret = apple_smc_read_flag(power->smc, SMC_KEY(CHCC));
+ if (ret < 0)
+ return ret;
+ else if (!ret)
+ return POWER_SUPPLY_STATUS_DISCHARGING;
+
+ /*
+ * If the AC input current limit is tiny or 0, we are discharging no matter
+ * how much the BMS believes it can charge.
+ */
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(AC-i), &ac_current);
+ if (!ret && ac_current < 100)
+ return POWER_SUPPLY_STATUS_DISCHARGING;
+
+ /* If the battery is full, report it as such. */
+ ret = apple_smc_read_flag(power->smc, SMC_KEY(BSFC));
+ if (ret < 0)
+ return ret;
+ else if (ret)
+ return POWER_SUPPLY_STATUS_FULL;
+
+ /*
+ * If we have charge limits supported and enabled and the SoC is > 75%,
+ * that means we are not charging for that reason (if not charging).
+ */
+ if (power->has_chwa && apple_smc_read_flag(power->smc, SMC_KEY(CHWA)) == 1) {
+ u8 buic = 0;
+
+ if (apple_smc_read_u8(power->smc, SMC_KEY(BUIC), &buic) >= 0 &&
+ buic >= CHWA_FIXED_START_THRESHOLD)
+ chwa_limit = true;
+ }
+
+ /* If there are reasons we aren't charging... */
+ ret = apple_smc_read_u64(power->smc, SMC_KEY(CHNC), &nocharge_flags);
+ if (!ret) {
+ /* Perhaps the battery is full after all */
+ if (nocharge_flags & CHNC_BATTERY_FULL)
+ return POWER_SUPPLY_STATUS_FULL;
+ /*
+ * Or maybe the BMS is just busy doing something, if so call it charging anyway.
+ * But CHWA limits show up as this, so exclude those.
+ */
+ else if (nocharge_flags == CHNC_BMS_BUSY && !chwa_limit)
+ return POWER_SUPPLY_STATUS_CHARGING;
+ /* If we have other reasons we aren't charging, say we aren't */
+ else if (nocharge_flags)
+ return POWER_SUPPLY_STATUS_NOT_CHARGING;
+ /* Else we're either charging or about to charge */
+ else
+ return POWER_SUPPLY_STATUS_CHARGING;
+ }
+
+ /* As a fallback, use the system charging flag. */
+ ret = apple_smc_read_flag(power->smc, SMC_KEY(CHSC));
+ if (ret < 0)
+ return ret;
+ if (!ret)
+ return POWER_SUPPLY_STATUS_NOT_CHARGING;
+ else
+ return POWER_SUPPLY_STATUS_CHARGING;
+}
+
+static int macsmc_battery_get_charge_behaviour(struct macsmc_power *power)
+{
+ int ret;
+ u8 val;
+
+ /* CH0I returns a bitmask like the low byte of CH0R */
+ ret = apple_smc_read_u8(power->smc, SMC_KEY(CH0I), &val);
+ if (ret)
+ return ret;
+ if (val & CH0R_NOAC_CH0I)
+ return POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE;
+
+ /* CH0C returns a bitmask containing CH0B/CH0C flags */
+ ret = apple_smc_read_u8(power->smc, SMC_KEY(CH0C), &val);
+ if (ret)
+ return ret;
+ if (val & CH0X_CH0C)
+ return POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE;
+ else
+ return POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO;
+}
+
+static int macsmc_battery_set_charge_behaviour(struct macsmc_power *power, int val)
+{
+ u8 ch0i, ch0c;
+ int ret;
+
+ /*
+ * CH0I/CH0C are "hard" controls that will allow the battery to run down to 0.
+ * CH0K/CH0B are "soft" controls that are reset to 0 when SOC drops below 50%;
+ * we don't expose these yet.
+ */
+
+ switch (val) {
+ case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO:
+ ch0i = ch0c = 0;
+ break;
+ case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE:
+ ch0i = 0;
+ ch0c = 1;
+ break;
+ case POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE:
+ ch0i = 1;
+ ch0c = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = apple_smc_write_u8(power->smc, SMC_KEY(CH0I), ch0i);
+ if (ret)
+ return ret;
+ return apple_smc_write_u8(power->smc, SMC_KEY(CH0C), ch0c);
+}
+
+static int macsmc_battery_get_date(const char *s, int *out)
+{
+ if (!isdigit(s[0]) || !isdigit(s[1]))
+ return -ENOTSUPP;
+
+ *out = (s[0] - '0') * 10 + s[1] - '0';
+ return 0;
+}
+
+static int macsmc_battery_get_capacity_level(struct macsmc_power *power)
+{
+ u32 val;
+ int ret;
+
+ /* Check for emergency shutdown condition */
+ if (apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &val) >= 0 && val)
+ return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
+
+ /* Check AC status for whether we could boot in this state */
+ if (apple_smc_read_u32(power->smc, SMC_KEY(ACSt), &val) >= 0) {
+ if (!(val & ACSt_CAN_BOOT_IBOOT))
+ return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
+
+ if (!(val & ACSt_CAN_BOOT_AP))
+ return POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+ }
+
+ /* Check battery full flag */
+ ret = apple_smc_read_flag(power->smc, SMC_KEY(BSFC));
+ if (ret > 0)
+ return POWER_SUPPLY_CAPACITY_LEVEL_FULL;
+ else if (ret == 0)
+ return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+ else
+ return POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
+}
+
+static int macsmc_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct macsmc_power *power = power_supply_get_drvdata(psy);
+ int ret = 0;
+ u8 vu8;
+ u16 vu16;
+ u32 vu32;
+ s16 vs16;
+ s32 vs32;
+ s64 vs64;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = macsmc_battery_get_status(power);
+ ret = val->intval < 0 ? val->intval : 0;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
+ val->intval = macsmc_battery_get_charge_behaviour(power);
+ ret = val->intval < 0 ? val->intval : 0;
+ break;
+ case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(B0TE), &vu16);
+ val->intval = vu16 == 0xffff ? 0 : vu16 * 60;
+ break;
+ case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW:
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(B0TF), &vu16);
+ val->intval = vu16 == 0xffff ? 0 : vu16 * 60;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ ret = apple_smc_read_u8(power->smc, SMC_KEY(BUIC), &vu8);
+ val->intval = vu8;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
+ val->intval = macsmc_battery_get_capacity_level(power);
+ ret = val->intval < 0 ? val->intval : 0;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(B0AV), &vu16);
+ val->intval = vu16 * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ ret = apple_smc_read_s16(power->smc, SMC_KEY(B0AC), &vs16);
+ val->intval = vs16 * 1000;
+ break;
+ case POWER_SUPPLY_PROP_POWER_NOW:
+ ret = apple_smc_read_s32(power->smc, SMC_KEY(B0AP), &vs32);
+ val->intval = vs32 * 1000;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(BITV), &vu16);
+ val->intval = vu16 * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RC), &vu16);
+ val->intval = vu16 * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ ret = apple_smc_read_u32(power->smc, SMC_KEY(CSIL), &vu32);
+ val->intval = vu32 * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RI), &vu16);
+ val->intval = vu16 * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RV), &vu16);
+ val->intval = vu16 * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(B0DC), &vu16);
+ val->intval = vu16 * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(B0FC), &vu16);
+ val->intval = vu16 * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RM), &vu16);
+ val->intval = swab16(vu16) * 1000;
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(B0AT), &vu16);
+ val->intval = vu16 - 2732;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_COUNTER:
+ ret = apple_smc_read_s64(power->smc, SMC_KEY(BAAC), &vs64);
+ val->intval = vs64;
+ break;
+ case POWER_SUPPLY_PROP_CYCLE_COUNT:
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(B0CT), &vu16);
+ val->intval = vu16;
+ break;
+ case POWER_SUPPLY_PROP_SCOPE:
+ val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ ret = apple_smc_read_flag(power->smc, SMC_KEY(BBAD));
+ val->intval = ret == 1 ? POWER_SUPPLY_HEALTH_DEAD : POWER_SUPPLY_HEALTH_GOOD;
+ ret = ret < 0 ? ret : 0;
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = power->model_name;
+ break;
+ case POWER_SUPPLY_PROP_SERIAL_NUMBER:
+ val->strval = power->serial_number;
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURE_YEAR:
+ ret = macsmc_battery_get_date(&power->mfg_date[0], &val->intval);
+ val->intval += 2000 - 8; /* -8 is a fixup for a firmware bug... */
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURE_MONTH:
+ ret = macsmc_battery_get_date(&power->mfg_date[2], &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURE_DAY:
+ ret = macsmc_battery_get_date(&power->mfg_date[4], &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD:
+ ret = apple_smc_read_flag(power->smc, SMC_KEY(CHWA));
+ val->intval = ret == 1 ? CHWA_FIXED_START_THRESHOLD : 100;
+ ret = ret < 0 ? ret : 0;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+ ret = apple_smc_read_flag(power->smc, SMC_KEY(CHWA));
+ val->intval = ret == 1 ? CHWA_FIXED_END_THRESHOLD : 100;
+ ret = ret < 0 ? ret : 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static int macsmc_battery_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct macsmc_power *power = power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
+ return macsmc_battery_set_charge_behaviour(power, val->intval);
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD:
+ /*
+ * Ignore, we allow writes so userspace isn't confused but this is
+ * not configurable independently, it always is 75 or 100 depending
+ * on the end_threshold boolean setting.
+ */
+ return 0;
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+ return apple_smc_write_flag(power->smc, SMC_KEY(CHWA),
+ val->intval <= CHWA_PROP_WRITE_THRESHOLD);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int macsmc_battery_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ struct macsmc_power *power = power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
+ return true;
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD:
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+ return power->has_chwa;
+ default:
+ return false;
+ }
+}
+
+static const enum power_supply_property macsmc_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
+ POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
+ POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_POWER_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_CHARGE_COUNTER,
+ POWER_SUPPLY_PROP_CYCLE_COUNT,
+ POWER_SUPPLY_PROP_SCOPE,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_SERIAL_NUMBER,
+ POWER_SUPPLY_PROP_MANUFACTURE_YEAR,
+ POWER_SUPPLY_PROP_MANUFACTURE_MONTH,
+ POWER_SUPPLY_PROP_MANUFACTURE_DAY,
+ POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD,
+ POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD
+};
+
+static const struct power_supply_desc macsmc_battery_desc = {
+ .name = "macsmc-battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .get_property = macsmc_battery_get_property,
+ .set_property = macsmc_battery_set_property,
+ .property_is_writeable = macsmc_battery_property_is_writeable,
+ .properties = macsmc_battery_props,
+ .num_properties = ARRAY_SIZE(macsmc_battery_props),
+};
+
+static int macsmc_ac_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct macsmc_power *power = power_supply_get_drvdata(psy);
+ int ret = 0;
+ u16 vu16;
+ u32 vu32;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ ret = apple_smc_read_u32(power->smc, SMC_KEY(CHIS), &vu32);
+ val->intval = !!vu32;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(AC-n), &vu16);
+ val->intval = vu16 * 1000;
+ break;
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ ret = apple_smc_read_u16(power->smc, SMC_KEY(AC-i), &vu16);
+ val->intval = vu16 * 1000;
+ break;
+ case POWER_SUPPLY_PROP_INPUT_POWER_LIMIT:
+ ret = apple_smc_read_u32(power->smc, SMC_KEY(ACPW), &vu32);
+ val->intval = vu32 * 1000;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static enum power_supply_property macsmc_ac_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
+ POWER_SUPPLY_PROP_INPUT_POWER_LIMIT,
+};
+
+static const struct power_supply_desc macsmc_ac_desc = {
+ .name = "macsmc-ac",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .get_property = macsmc_ac_get_property,
+ .properties = macsmc_ac_props,
+ .num_properties = ARRAY_SIZE(macsmc_ac_props),
+};
+
+static int macsmc_log_power_set(const char *val, const struct kernel_param *kp)
+{
+ int ret = param_set_bool(val, kp);
+
+ if (ret < 0)
+ return ret;
+
+ if (log_power && g_power)
+ schedule_delayed_work(&g_power->dbg_log_work, 0);
+
+ return 0;
+}
+
+static void macsmc_dbg_work(struct work_struct *wrk)
+{
+ struct macsmc_power *power = container_of(to_delayed_work(wrk),
+ struct macsmc_power, dbg_log_work);
+
+ macsmc_do_dbg(power);
+
+ if (log_power)
+ schedule_delayed_work(&power->dbg_log_work, POWER_LOG_INTERVAL);
+}
+
+static void macsmc_power_critical_work(struct work_struct *wrk)
+{
+ struct macsmc_power *power = container_of(wrk, struct macsmc_power, critical_work);
+ int ret;
+ u32 bcf0;
+ u16 bitv, b0av;
+
+ /*
+ * Check if the battery voltage is below the design voltage. If it is,
+ * we have a few seconds until the machine dies. Explicitly shut down,
+ * which at least gets the NVMe controller to flush its cache.
+ */
+ if (apple_smc_read_u16(power->smc, SMC_KEY(BITV), &bitv) >= 0 &&
+ apple_smc_read_u16(power->smc, SMC_KEY(B0AV), &b0av) >= 0 &&
+ b0av < bitv) {
+ dev_crit(power->dev, "Emergency notification: Battery is critical\n");
+ if (kernel_can_power_off())
+ kernel_power_off();
+ else /* Missing macsmc-reboot driver? In this state, this will not boot anyway. */
+ kernel_restart("Battery is critical");
+ }
+
+ /* This spams once per second, so make sure we only trigger shutdown once. */
+ if (power->shutdown_started)
+ return;
+
+ /* Check for battery empty condition */
+ ret = apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &bcf0);
+ if (ret < 0) {
+ dev_err(power->dev,
+ "Emergency notification: Failed to read battery status\n");
+ } else if (bcf0 == 0) {
+ dev_warn(power->dev, "Emergency notification: Battery status is OK?\n");
+ return;
+ } else {
+ dev_warn(power->dev, "Emergency notification: Battery is empty\n");
+ }
+
+ power->shutdown_started = true;
+
+ /*
+ * Attempt to trigger an orderly shutdown. At this point, we should have a few
+ * minutes of reserve capacity left, enough to do a clean shutdown.
+ */
+ dev_warn(power->dev, "Shutting down in 10 seconds\n");
+ ssleep(10);
+
+ /*
+ * Don't force it; if this stalls or fails, the last-resort check above will
+ * trigger a hard shutdown when shutdown is truly imminent.
+ */
+ orderly_poweroff(false);
+}
+
+static int macsmc_power_event(struct notifier_block *nb, unsigned long event, void *data)
+{
+ struct macsmc_power *power = container_of(nb, struct macsmc_power, nb);
+
+ if ((event & 0xffffff00) == 0x71010100) {
+ bool charging = (event & 0xff) != 0;
+
+ dev_info(power->dev, "Charging: %d\n", charging);
+ power_supply_changed(power->batt);
+ power_supply_changed(power->ac);
+
+ return NOTIFY_OK;
+ } else if (event == 0x71020000) {
+ schedule_work(&power->critical_work);
+
+ return NOTIFY_OK;
+ } else if ((event & 0xffff0000) == 0x71060000) {
+ u8 changed_port = event >> 8;
+ u8 cur_port;
+
+ /* Port charging state change? */
+ if (apple_smc_read_u8(power->smc, SMC_KEY(AC-W), &cur_port) >= 0) {
+ dev_info(power->dev, "Port %d state change (charge port: %d)\n",
+ changed_port + 1, cur_port);
+ }
+
+ power_supply_changed(power->batt);
+ power_supply_changed(power->ac);
+
+ return NOTIFY_OK;
+ } else if ((event & 0xff000000) == 0x71000000) {
+ dev_info(power->dev, "Unknown charger event 0x%lx\n", event);
+
+ return NOTIFY_OK;
+ } else if ((event & 0xffff0000) == 0x72010000) {
+ /* Button event handled by macsmc-hid, but let's do a debug print */
+ if (log_power)
+ macsmc_do_dbg(power);
+
+ return NOTIFY_OK;
+ }
+
+ return NOTIFY_DONE;
+}
+
+static int macsmc_power_probe(struct platform_device *pdev)
+{
+ struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent);
+ struct power_supply_config psy_cfg = {};
+ struct macsmc_power *power;
+ u32 val;
+ int ret;
+
+ power = devm_kzalloc(&pdev->dev, sizeof(*power), GFP_KERNEL);
+ if (!power)
+ return -ENOMEM;
+
+ power->dev = &pdev->dev;
+ power->smc = smc;
+ power->batt_desc = macsmc_battery_desc;
+ dev_set_drvdata(&pdev->dev, power);
+
+ /* Ignore devices without a charger/battery */
+ if (macsmc_battery_get_status(power) <= POWER_SUPPLY_STATUS_UNKNOWN)
+ return -ENODEV;
+
+ /* Fetch string properties */
+ 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);
+
+ /* Turn off the "optimized battery charging" flags, in case macOS left them on */
+ apple_smc_write_u8(power->smc, SMC_KEY(CH0K), 0);
+ apple_smc_write_u8(power->smc, SMC_KEY(CH0B), 0);
+
+ if (apple_smc_read_flag(power->smc, SMC_KEY(CHWA)) >= 0) {
+ power->has_chwa = true;
+ } else {
+ /* Remove the last 2 properties that control the charge threshold */
+ power->batt_desc.num_properties -= 2;
+ }
+
+ /* Doing one read of this flag enables critical shutdown notifications */
+ apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &val);
+
+ psy_cfg.drv_data = power;
+ power->batt = devm_power_supply_register(&pdev->dev, &power->batt_desc, &psy_cfg);
+ if (IS_ERR(power->batt)) {
+ dev_err(&pdev->dev, "Failed to register battery\n");
+ ret = PTR_ERR(power->batt);
+ return ret;
+ }
+
+ power->ac = devm_power_supply_register(&pdev->dev, &macsmc_ac_desc, &psy_cfg);
+ if (IS_ERR(power->ac)) {
+ dev_err(&pdev->dev, "Failed to register AC adapter\n");
+ ret = PTR_ERR(power->ac);
+ return ret;
+ }
+
+ power->nb.notifier_call = macsmc_power_event;
+ apple_smc_register_notifier(power->smc, &power->nb);
+
+ INIT_WORK(&power->critical_work, macsmc_power_critical_work);
+ INIT_DELAYED_WORK(&power->dbg_log_work, macsmc_dbg_work);
+
+ g_power = power;
+
+ if (log_power)
+ schedule_delayed_work(&power->dbg_log_work, 0);
+
+ return 0;
+}
+
+static int macsmc_power_remove(struct platform_device *pdev)
+{
+ struct macsmc_power *power = dev_get_drvdata(&pdev->dev);
+
+ cancel_work(&power->critical_work);
+ cancel_delayed_work(&power->dbg_log_work);
+
+ g_power = NULL;
+
+ apple_smc_unregister_notifier(power->smc, &power->nb);
+
+ return 0;
+}
+
+static struct platform_driver macsmc_power_driver = {
+ .driver = {
+ .name = "macsmc-power",
+ .owner = THIS_MODULE,
+ },
+ .probe = macsmc_power_probe,
+ .remove = macsmc_power_remove,
+};
+module_platform_driver(macsmc_power_driver);
+
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_DESCRIPTION("Apple SMC battery and power management driver");
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
+MODULE_ALIAS("platform:macsmc-power");
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 3814e08..4a46055 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -1995,4 +1995,17 @@
This driver can also be built as a module, if so, the module
will be called "rtc-ssd20xd".
+config RTC_DRV_MACSMC
+ tristate "Apple Mac SMC RTC"
+ depends on ARCH_APPLE || COMPILE_TEST
+ depends on APPLE_SMC
+ depends on OF
+ default ARCH_APPLE
+ help
+ If you say yes here you get support for RTC functions
+ inside Apple SPMI PMUs.
+
+ To compile this driver as a module, choose M here: the
+ module will be called rtc-macsmc.
+
endif # RTC_CLASS
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 7b03c3a..c9cc3b1 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -88,6 +88,7 @@
obj-$(CONFIG_RTC_DRV_M48T35) += rtc-m48t35.o
obj-$(CONFIG_RTC_DRV_M48T59) += rtc-m48t59.o
obj-$(CONFIG_RTC_DRV_M48T86) += rtc-m48t86.o
+obj-$(CONFIG_RTC_DRV_MACSMC) += rtc-macsmc.o
obj-$(CONFIG_RTC_DRV_MAX6900) += rtc-max6900.o
obj-$(CONFIG_RTC_DRV_MAX6902) += rtc-max6902.o
obj-$(CONFIG_RTC_DRV_MAX6916) += rtc-max6916.o
diff --git a/drivers/rtc/rtc-macsmc.c b/drivers/rtc/rtc-macsmc.c
new file mode 100644
index 0000000..34730c9
--- /dev/null
+++ b/drivers/rtc/rtc-macsmc.c
@@ -0,0 +1,130 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple SMC RTC driver
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include <linux/bitops.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/macsmc.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/rtc.h>
+#include <linux/slab.h>
+
+/* 48-bit RTC */
+#define RTC_BYTES 6
+#define RTC_BITS (8 * RTC_BYTES)
+
+/* 32768 Hz clock */
+#define RTC_SEC_SHIFT 15
+
+struct macsmc_rtc {
+ struct device *dev;
+ struct apple_smc *smc;
+ struct rtc_device *rtc_dev;
+ struct nvmem_cell *rtc_offset;
+};
+
+static int macsmc_rtc_get_time(struct device *dev, struct rtc_time *tm)
+{
+ struct macsmc_rtc *rtc = dev_get_drvdata(dev);
+ u64 ctr = 0, off = 0;
+ time64_t now;
+ void *p_off;
+ size_t len;
+ int ret;
+
+ ret = apple_smc_read(rtc->smc, SMC_KEY(CLKM), &ctr, RTC_BYTES);
+ if (ret != RTC_BYTES)
+ return ret < 0 ? ret : -EIO;
+
+ p_off = nvmem_cell_read(rtc->rtc_offset, &len);
+ if (IS_ERR(p_off))
+ return PTR_ERR(p_off);
+ if (len < RTC_BYTES) {
+ kfree(p_off);
+ return -EIO;
+ }
+
+ memcpy(&off, p_off, RTC_BYTES);
+ kfree(p_off);
+
+ /* Sign extend from 48 to 64 bits, then arithmetic shift right 15 bits to get seconds */
+ now = sign_extend64(ctr + off, RTC_BITS - 1) >> RTC_SEC_SHIFT;
+ rtc_time64_to_tm(now, tm);
+
+ return ret;
+}
+
+static int macsmc_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+ struct macsmc_rtc *rtc = dev_get_drvdata(dev);
+ u64 ctr = 0, off = 0;
+ int ret;
+
+ ret = apple_smc_read(rtc->smc, SMC_KEY(CLKM), &ctr, RTC_BYTES);
+ if (ret != RTC_BYTES)
+ return ret < 0 ? ret : -EIO;
+
+ /* This sets the offset such that the set second begins now */
+ off = (rtc_tm_to_time64(tm) << RTC_SEC_SHIFT) - ctr;
+ return nvmem_cell_write(rtc->rtc_offset, &off, RTC_BYTES);
+}
+
+static const struct rtc_class_ops macsmc_rtc_ops = {
+ .read_time = macsmc_rtc_get_time,
+ .set_time = macsmc_rtc_set_time,
+};
+
+static int macsmc_rtc_probe(struct platform_device *pdev)
+{
+ struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent);
+ struct macsmc_rtc *rtc;
+
+ /* Ignore devices without this functionality */
+ if (!apple_smc_key_exists(smc, SMC_KEY(CLKM)))
+ return -ENODEV;
+
+ rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL);
+ if (!rtc)
+ return -ENOMEM;
+
+ rtc->dev = &pdev->dev;
+ rtc->smc = smc;
+
+ pdev->dev.of_node = of_get_child_by_name(pdev->dev.parent->of_node, "rtc");
+
+ rtc->rtc_offset = devm_nvmem_cell_get(&pdev->dev, "rtc_offset");
+ if (IS_ERR(rtc->rtc_offset))
+ return dev_err_probe(&pdev->dev, PTR_ERR(rtc->rtc_offset),
+ "Failed to get rtc_offset NVMEM cell\n");
+
+ rtc->rtc_dev = devm_rtc_allocate_device(&pdev->dev);
+ if (IS_ERR(rtc->rtc_dev))
+ return PTR_ERR(rtc->rtc_dev);
+
+ rtc->rtc_dev->ops = &macsmc_rtc_ops;
+ rtc->rtc_dev->range_min = S64_MIN >> (RTC_SEC_SHIFT + (64 - RTC_BITS));
+ rtc->rtc_dev->range_max = S64_MAX >> (RTC_SEC_SHIFT + (64 - RTC_BITS));
+
+ platform_set_drvdata(pdev, rtc);
+
+ return devm_rtc_register_device(rtc->rtc_dev);
+}
+
+static struct platform_driver macsmc_rtc_driver = {
+ .driver = {
+ .name = "macsmc-rtc",
+ .owner = THIS_MODULE,
+ },
+ .probe = macsmc_rtc_probe,
+};
+module_platform_driver(macsmc_rtc_driver);
+
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_DESCRIPTION("Apple SMC RTC driver");
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
+MODULE_ALIAS("platform:macsmc-rtc");
diff --git a/drivers/soc/apple/Kconfig b/drivers/soc/apple/Kconfig
index eff486a..5b23eb3 100644
--- a/drivers/soc/apple/Kconfig
+++ b/drivers/soc/apple/Kconfig
@@ -4,9 +4,44 @@
menu "Apple SoC drivers"
+config APPLE_PMGR_PWRSTATE
+ bool "Apple SoC PMGR power state control"
+ depends on PM
+ select REGMAP
+ select MFD_SYSCON
+ select PM_GENERIC_DOMAINS
+ select RESET_CONTROLLER
+ default ARCH_APPLE
+ help
+ The PMGR block in Apple SoCs provides high-level power state
+ controls for SoC devices. This driver manages them through the
+ generic power domain framework, and also provides reset support.
+
+config APPLE_PMGR_MISC
+ bool "Apple SoC PMGR miscellaneous support"
+ depends on PM
+ default ARCH_APPLE
+ help
+ The PMGR block in Apple SoCs provides high-level power state
+ controls for SoC devices. This driver manages miscellaneous
+ power controls.
+
+config APPLE_MBOX
+ tristate "Apple SoC mailboxes"
+ depends on PM
+ depends on ARCH_APPLE || (64BIT && COMPILE_TEST)
+ default ARCH_APPLE
+ help
+ Apple SoCs have various co-processors required for certain
+ peripherals to work (NVMe, display controller, etc.). This
+ driver adds support for the mailbox controller used to
+ communicate with those.
+
+ Say Y here if you have a Apple SoC.
+
config APPLE_RTKIT
tristate "Apple RTKit co-processor IPC protocol"
- depends on MAILBOX
+ depends on APPLE_MBOX
depends on ARCH_APPLE || COMPILE_TEST
default ARCH_APPLE
help
@@ -17,6 +52,20 @@
Say 'y' here if you have an Apple SoC.
+config APPLE_RTKIT_HELPER
+ tristate "Apple Generic RTKit helper co-processor"
+ depends on APPLE_RTKIT
+ depends on ARCH_APPLE || COMPILE_TEST
+ default ARCH_APPLE
+ help
+ Apple SoCs such as the M1 come with various co-processors running
+ their proprietary RTKit operating system. This option enables support
+ for a generic co-processor that does not implement any additional
+ in-band communications. It can be used for testing purposes, or for
+ coprocessors such as MTP that communicate over a different interface.
+
+ Say 'y' here if you have an Apple SoC.
+
config APPLE_SART
tristate "Apple SART DMA address filter"
depends on ARCH_APPLE || COMPILE_TEST
@@ -28,6 +77,16 @@
Say 'y' here if you have an Apple SoC.
+config APPLE_DOCKCHANNEL
+ tristate "Apple DockChannel FIFO"
+ depends on ARCH_APPLE || COMPILE_TEST
+ default ARCH_APPLE
+ help
+ DockChannel is a simple FIFO used on Apple SoCs for debug and inter-processor
+ communications.
+
+ Say 'y' here if you have an Apple SoC.
+
endmenu
endif
diff --git a/drivers/soc/apple/Makefile b/drivers/soc/apple/Makefile
index b241e6a..5ab0ae8 100644
--- a/drivers/soc/apple/Makefile
+++ b/drivers/soc/apple/Makefile
@@ -1,6 +1,17 @@
# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_APPLE_PMGR_MISC) += apple-pmgr-misc.o
+
+obj-$(CONFIG_APPLE_MBOX) += apple-mailbox.o
+apple-mailbox-y = mailbox.o
+
obj-$(CONFIG_APPLE_RTKIT) += apple-rtkit.o
apple-rtkit-y = rtkit.o rtkit-crashlog.o
+obj-$(CONFIG_APPLE_RTKIT_HELPER) += apple-rtkit-helper.o
+apple-rtkit-helper-y = rtkit-helper.o
+
obj-$(CONFIG_APPLE_SART) += apple-sart.o
apple-sart-y = sart.o
+
+obj-$(CONFIG_APPLE_DOCKCHANNEL) += apple-dockchannel.o
+apple-dockchannel-y = dockchannel.o
diff --git a/drivers/soc/apple/apple-pmgr-misc.c b/drivers/soc/apple/apple-pmgr-misc.c
new file mode 100644
index 0000000..e768f34
--- /dev/null
+++ b/drivers/soc/apple/apple-pmgr-misc.c
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple SoC PMGR device power state driver
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include <linux/bitops.h>
+#include <linux/bitfield.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+
+#define APPLE_CLKGEN_PSTATE 0
+#define APPLE_CLKGEN_PSTATE_DESIRED GENMASK(3, 0)
+
+#define SYS_DEV_PSTATE_SUSPEND 1
+
+enum sys_device {
+ DEV_FABRIC,
+ DEV_DCS,
+ DEV_MAX,
+};
+
+struct apple_pmgr_sys_device {
+ void __iomem *base;
+ u32 active_state;
+ u32 suspend_state;
+};
+
+struct apple_pmgr_misc {
+ struct device *dev;
+ struct apple_pmgr_sys_device devices[DEV_MAX];
+};
+
+static void apple_pmgr_sys_dev_set_pstate(struct apple_pmgr_misc *misc,
+ enum sys_device dev, bool active)
+{
+ u32 pstate;
+ u32 val;
+
+ if (!misc->devices[dev].base)
+ return;
+
+ if (active)
+ pstate = misc->devices[dev].active_state;
+ else
+ pstate = misc->devices[dev].suspend_state;
+
+ printk("set %d ps to pstate %d\n", dev, pstate);
+
+ val = readl_relaxed(misc->devices[dev].base + APPLE_CLKGEN_PSTATE);
+ val &= ~APPLE_CLKGEN_PSTATE_DESIRED;
+ val |= FIELD_PREP(APPLE_CLKGEN_PSTATE_DESIRED, pstate);
+ writel_relaxed(val, misc->devices[dev].base);
+}
+
+static int __maybe_unused apple_pmgr_misc_suspend_noirq(struct device *dev)
+{
+ struct apple_pmgr_misc *misc = dev_get_drvdata(dev);
+ int i;
+
+ for (i = 0; i < DEV_MAX; i++)
+ apple_pmgr_sys_dev_set_pstate(misc, i, false);
+
+ return 0;
+}
+
+static int __maybe_unused apple_pmgr_misc_resume_noirq(struct device *dev)
+{
+ struct apple_pmgr_misc *misc = dev_get_drvdata(dev);
+ int i;
+
+ for (i = 0; i < DEV_MAX; i++)
+ apple_pmgr_sys_dev_set_pstate(misc, i, true);
+
+ return 0;
+}
+
+static bool apple_pmgr_init_device(struct apple_pmgr_misc *misc,
+ enum sys_device dev, const char *device_name)
+{
+ void __iomem *base;
+ char name[32];
+ u32 val;
+
+ snprintf(name, sizeof(name), "%s-ps", device_name);
+
+ base = devm_platform_ioremap_resource_byname(
+ to_platform_device(misc->dev), name);
+ if (!base)
+ return false;
+
+ val = readl_relaxed(base + APPLE_CLKGEN_PSTATE);
+
+ misc->devices[dev].base = base;
+ misc->devices[dev].active_state =
+ FIELD_GET(APPLE_CLKGEN_PSTATE_DESIRED, val);
+ misc->devices[dev].suspend_state = SYS_DEV_PSTATE_SUSPEND;
+
+ snprintf(name, sizeof(name), "apple,%s-min-ps", device_name);
+ of_property_read_u32(misc->dev->of_node, name,
+ &misc->devices[dev].suspend_state);
+
+ return true;
+}
+
+static int apple_pmgr_misc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct apple_pmgr_misc *misc;
+ int ret = -ENODEV;
+
+ misc = devm_kzalloc(dev, sizeof(*misc), GFP_KERNEL);
+ if (!misc)
+ return -ENOMEM;
+
+ misc->dev = dev;
+
+ if (apple_pmgr_init_device(misc, DEV_FABRIC, "fabric"))
+ ret = 0;
+
+ if (apple_pmgr_init_device(misc, DEV_DCS, "dcs"))
+ ret = 0;
+
+ platform_set_drvdata(pdev, misc);
+
+ return ret;
+}
+
+static const struct of_device_id apple_pmgr_misc_of_match[] = {
+ { .compatible = "apple,t6000-pmgr-misc" },
+ {}
+};
+
+MODULE_DEVICE_TABLE(of, apple_pmgr_misc_of_match);
+
+static const struct dev_pm_ops apple_pmgr_misc_pm_ops = {
+ SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(apple_pmgr_misc_suspend_noirq,
+ apple_pmgr_misc_resume_noirq)
+};
+
+static struct platform_driver apple_pmgr_misc_driver = {
+ .probe = apple_pmgr_misc_probe,
+ .driver = {
+ .name = "apple-pmgr-misc",
+ .of_match_table = apple_pmgr_misc_of_match,
+ .pm = pm_ptr(&apple_pmgr_misc_pm_ops),
+ },
+};
+
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
+MODULE_DESCRIPTION("PMGR misc driver for Apple SoCs");
+MODULE_LICENSE("GPL v2");
+
+module_platform_driver(apple_pmgr_misc_driver);
diff --git a/drivers/soc/apple/dockchannel.c b/drivers/soc/apple/dockchannel.c
new file mode 100644
index 0000000..b4d793b
--- /dev/null
+++ b/drivers/soc/apple/dockchannel.c
@@ -0,0 +1,407 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple DockChannel FIFO driver
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include <asm/unaligned.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/irq.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/irqdomain.h>
+#include <linux/soc/apple/dockchannel.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+
+#define DOCKCHANNEL_MAX_IRQ 32
+
+#define DOCKCHANNEL_TX_TIMEOUT_MS 1000
+#define DOCKCHANNEL_RX_TIMEOUT_MS 1000
+
+#define IRQ_MASK 0x0
+#define IRQ_FLAG 0x4
+
+#define IRQ_TX BIT(0)
+#define IRQ_RX BIT(1)
+
+#define CONFIG_TX_THRESH 0x0
+#define CONFIG_RX_THRESH 0x4
+
+#define DATA_TX8 0x4
+#define DATA_TX16 0x8
+#define DATA_TX24 0xc
+#define DATA_TX32 0x10
+#define DATA_TX_FREE 0x14
+#define DATA_RX8 0x1c
+#define DATA_RX16 0x20
+#define DATA_RX24 0x24
+#define DATA_RX32 0x28
+#define DATA_RX_COUNT 0x2c
+
+struct dockchannel {
+ struct device *dev;
+ int tx_irq;
+ int rx_irq;
+
+ void __iomem *config_base;
+ void __iomem *data_base;
+
+ u32 fifo_size;
+ bool awaiting;
+ struct completion tx_comp;
+ struct completion rx_comp;
+
+ void *cookie;
+ void (*data_available)(void *cookie, size_t avail);
+};
+
+struct dockchannel_common {
+ struct device *dev;
+ struct irq_domain *domain;
+ int irq;
+
+ void __iomem *irq_base;
+};
+
+/* Dockchannel FIFO functions */
+
+static irqreturn_t dockchannel_tx_irq(int irq, void *data)
+{
+ struct dockchannel *dockchannel = data;
+
+ disable_irq_nosync(irq);
+ complete(&dockchannel->tx_comp);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t dockchannel_rx_irq(int irq, void *data)
+{
+ struct dockchannel *dockchannel = data;
+
+ disable_irq_nosync(irq);
+
+ if (dockchannel->awaiting) {
+ return IRQ_WAKE_THREAD;
+ } else {
+ complete(&dockchannel->rx_comp);
+ return IRQ_HANDLED;
+ }
+}
+
+static irqreturn_t dockchannel_rx_irq_thread(int irq, void *data)
+{
+ struct dockchannel *dockchannel = data;
+ size_t avail = readl_relaxed(dockchannel->data_base + DATA_RX_COUNT);
+
+ dockchannel->awaiting = false;
+ dockchannel->data_available(dockchannel->cookie, avail);
+
+ return IRQ_HANDLED;
+}
+
+int dockchannel_send(struct dockchannel *dockchannel, const void *buf, size_t count)
+{
+ size_t left = count;
+ const u8 *p = buf;
+
+ while (left > 0) {
+ size_t avail = readl_relaxed(dockchannel->data_base + DATA_TX_FREE);
+ size_t block = min(left, avail);
+
+ if (avail == 0) {
+ size_t threshold = min((size_t)(dockchannel->fifo_size / 2), left);
+
+ writel_relaxed(threshold, dockchannel->config_base + CONFIG_TX_THRESH);
+ reinit_completion(&dockchannel->tx_comp);
+ enable_irq(dockchannel->tx_irq);
+
+ if (!wait_for_completion_timeout(&dockchannel->tx_comp,
+ msecs_to_jiffies(DOCKCHANNEL_TX_TIMEOUT_MS))) {
+ disable_irq(dockchannel->tx_irq);
+ return -ETIMEDOUT;
+ }
+
+ continue;
+ }
+
+ while (block >= 4) {
+ writel_relaxed(get_unaligned_le32(p), dockchannel->data_base + DATA_TX32);
+ p += 4;
+ left -= 4;
+ block -= 4;
+ }
+ while (block > 0) {
+ writeb_relaxed(*p++, dockchannel->data_base + DATA_TX8);
+ left--;
+ block--;
+ }
+ }
+
+ return count;
+}
+EXPORT_SYMBOL(dockchannel_send);
+
+int dockchannel_recv(struct dockchannel *dockchannel, void *buf, size_t count)
+{
+ size_t left = count;
+ u8 *p = buf;
+
+ while (left > 0) {
+ size_t avail = readl_relaxed(dockchannel->data_base + DATA_RX_COUNT);
+ size_t block = min(left, avail);
+
+ if (avail == 0) {
+ size_t threshold = min((size_t)(dockchannel->fifo_size / 2), left);
+
+ writel_relaxed(threshold, dockchannel->config_base + CONFIG_RX_THRESH);
+ reinit_completion(&dockchannel->rx_comp);
+ enable_irq(dockchannel->rx_irq);
+
+ if (!wait_for_completion_timeout(&dockchannel->rx_comp,
+ msecs_to_jiffies(DOCKCHANNEL_RX_TIMEOUT_MS))) {
+ disable_irq(dockchannel->rx_irq);
+ return -ETIMEDOUT;
+ }
+
+ continue;
+ }
+
+ while (block >= 4) {
+ put_unaligned_le32(readl_relaxed(dockchannel->data_base + DATA_RX32), p);
+ p += 4;
+ left -= 4;
+ block -= 4;
+ }
+ while (block > 0) {
+ *p++ = readl_relaxed(dockchannel->data_base + DATA_RX8) >> 8;
+ left--;
+ block--;
+ }
+ }
+
+ return count;
+}
+EXPORT_SYMBOL(dockchannel_recv);
+
+int dockchannel_await(struct dockchannel *dockchannel,
+ void (*callback)(void *cookie, size_t avail),
+ void *cookie, size_t count)
+{
+ size_t threshold = min((size_t)dockchannel->fifo_size, count);
+
+ if (!count) {
+ dockchannel->awaiting = false;
+ disable_irq(dockchannel->rx_irq);
+ return 0;
+ }
+
+ dockchannel->data_available = callback;
+ dockchannel->cookie = cookie;
+ dockchannel->awaiting = true;
+ writel_relaxed(threshold, dockchannel->config_base + CONFIG_RX_THRESH);
+ enable_irq(dockchannel->rx_irq);
+
+ return threshold;
+}
+EXPORT_SYMBOL(dockchannel_await);
+
+struct dockchannel *dockchannel_init(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct dockchannel *dockchannel;
+ int ret;
+
+ dockchannel = devm_kzalloc(dev, sizeof(*dockchannel), GFP_KERNEL);
+ if (!dockchannel)
+ return ERR_PTR(-ENOMEM);
+
+ dockchannel->dev = dev;
+ dockchannel->config_base = devm_platform_ioremap_resource_byname(pdev, "config");
+ if (IS_ERR(dockchannel->config_base))
+ return (__force void *)dockchannel->config_base;
+
+ dockchannel->data_base = devm_platform_ioremap_resource_byname(pdev, "data");
+ if (IS_ERR(dockchannel->data_base))
+ return (__force void *)dockchannel->data_base;
+
+ ret = of_property_read_u32(dev->of_node, "apple,fifo-size", &dockchannel->fifo_size);
+ if (ret)
+ return ERR_PTR(dev_err_probe(dev, ret, "Missing apple,fifo-size property"));
+
+ init_completion(&dockchannel->tx_comp);
+ init_completion(&dockchannel->rx_comp);
+
+ dockchannel->tx_irq = platform_get_irq_byname(pdev, "tx");
+ if (dockchannel->tx_irq <= 0) {
+ return ERR_PTR(dev_err_probe(dev, dockchannel->tx_irq,
+ "Failed to get TX IRQ"));
+ }
+
+ dockchannel->rx_irq = platform_get_irq_byname(pdev, "rx");
+ if (dockchannel->rx_irq <= 0) {
+ return ERR_PTR(dev_err_probe(dev, dockchannel->rx_irq,
+ "Failed to get RX IRQ"));
+ }
+
+ ret = devm_request_irq(dev, dockchannel->tx_irq, dockchannel_tx_irq, IRQF_NO_AUTOEN,
+ "apple-dockchannel-tx", dockchannel);
+ if (ret)
+ return ERR_PTR(dev_err_probe(dev, ret, "Failed to request TX IRQ"));
+
+ ret = devm_request_threaded_irq(dev, dockchannel->rx_irq, dockchannel_rx_irq,
+ dockchannel_rx_irq_thread, IRQF_NO_AUTOEN,
+ "apple-dockchannel-rx", dockchannel);
+ if (ret)
+ return ERR_PTR(dev_err_probe(dev, ret, "Failed to request RX IRQ"));
+
+ return dockchannel;
+}
+EXPORT_SYMBOL(dockchannel_init);
+
+
+/* Dockchannel IRQchip */
+
+static void dockchannel_irq(struct irq_desc *desc)
+{
+ unsigned int irq = irq_desc_get_irq(desc);
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ struct dockchannel_common *dcc = irq_get_handler_data(irq);
+ unsigned long flags = readl_relaxed(dcc->irq_base + IRQ_FLAG);
+ int bit;
+
+ chained_irq_enter(chip, desc);
+
+ for_each_set_bit(bit, &flags, DOCKCHANNEL_MAX_IRQ)
+ generic_handle_domain_irq(dcc->domain, bit);
+
+ chained_irq_exit(chip, desc);
+}
+
+static void dockchannel_irq_ack(struct irq_data *data)
+{
+ struct dockchannel_common *dcc = irq_data_get_irq_chip_data(data);
+ unsigned int hwirq = data->hwirq;
+
+ writel_relaxed(BIT(hwirq), dcc->irq_base + IRQ_FLAG);
+}
+
+static void dockchannel_irq_mask(struct irq_data *data)
+{
+ struct dockchannel_common *dcc = irq_data_get_irq_chip_data(data);
+ unsigned int hwirq = data->hwirq;
+ u32 val = readl_relaxed(dcc->irq_base + IRQ_MASK);
+
+ writel_relaxed(val & ~BIT(hwirq), dcc->irq_base + IRQ_MASK);
+}
+
+static void dockchannel_irq_unmask(struct irq_data *data)
+{
+ struct dockchannel_common *dcc = irq_data_get_irq_chip_data(data);
+ unsigned int hwirq = data->hwirq;
+ u32 val = readl_relaxed(dcc->irq_base + IRQ_MASK);
+
+ writel_relaxed(val | BIT(hwirq), dcc->irq_base + IRQ_MASK);
+}
+
+static const struct irq_chip dockchannel_irqchip = {
+ .name = "dockchannel-irqc",
+ .irq_ack = dockchannel_irq_ack,
+ .irq_mask = dockchannel_irq_mask,
+ .irq_unmask = dockchannel_irq_unmask,
+};
+
+static int dockchannel_irq_domain_map(struct irq_domain *d, unsigned int virq,
+ irq_hw_number_t hw)
+{
+ irq_set_chip_data(virq, d->host_data);
+ irq_set_chip_and_handler(virq, &dockchannel_irqchip, handle_level_irq);
+
+ return 0;
+}
+
+static const struct irq_domain_ops dockchannel_irq_domain_ops = {
+ .xlate = irq_domain_xlate_twocell,
+ .map = dockchannel_irq_domain_map,
+};
+
+static int dockchannel_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct dockchannel_common *dcc;
+ struct device_node *child;
+
+ dcc = devm_kzalloc(dev, sizeof(*dcc), GFP_KERNEL);
+ if (!dcc)
+ return -ENOMEM;
+
+ dcc->dev = dev;
+ platform_set_drvdata(pdev, dcc);
+
+ dcc->irq_base = devm_platform_ioremap_resource_byname(pdev, "irq");
+ if (IS_ERR(dcc->irq_base))
+ return PTR_ERR(dcc->irq_base);
+
+ writel_relaxed(0, dcc->irq_base + IRQ_MASK);
+ writel_relaxed(~0, dcc->irq_base + IRQ_FLAG);
+
+ dcc->domain = irq_domain_add_linear(dev->of_node, DOCKCHANNEL_MAX_IRQ,
+ &dockchannel_irq_domain_ops, dcc);
+ if (!dcc->domain)
+ return -ENOMEM;
+
+ dcc->irq = platform_get_irq(pdev, 0);
+ if (dcc->irq <= 0)
+ return dev_err_probe(dev, dcc->irq, "Failed to get IRQ");
+
+ irq_set_handler_data(dcc->irq, dcc);
+ irq_set_chained_handler(dcc->irq, dockchannel_irq);
+
+ for_each_child_of_node(dev->of_node, child)
+ of_platform_device_create(child, NULL, dev);
+
+ return 0;
+}
+
+static int dockchannel_remove(struct platform_device *pdev)
+{
+ struct dockchannel_common *dcc = platform_get_drvdata(pdev);
+ int hwirq;
+
+ device_for_each_child(&pdev->dev, NULL, of_platform_device_destroy);
+
+ irq_set_chained_handler_and_data(dcc->irq, NULL, NULL);
+
+ for (hwirq = 0; hwirq < DOCKCHANNEL_MAX_IRQ; hwirq++)
+ irq_dispose_mapping(irq_find_mapping(dcc->domain, hwirq));
+
+ irq_domain_remove(dcc->domain);
+
+ writel_relaxed(0, dcc->irq_base + IRQ_MASK);
+ writel_relaxed(~0, dcc->irq_base + IRQ_FLAG);
+
+ return 0;
+}
+
+static const struct of_device_id dockchannel_of_match[] = {
+ { .compatible = "apple,dockchannel" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, dockchannel_of_match);
+
+static struct platform_driver dockchannel_driver = {
+ .driver = {
+ .name = "dockchannel",
+ .of_match_table = dockchannel_of_match,
+ },
+ .probe = dockchannel_probe,
+ .remove = dockchannel_remove,
+};
+module_platform_driver(dockchannel_driver);
+
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_DESCRIPTION("Apple DockChannel driver");
diff --git a/drivers/soc/apple/mailbox.c b/drivers/soc/apple/mailbox.c
new file mode 100644
index 0000000..eeeeccd
--- /dev/null
+++ b/drivers/soc/apple/mailbox.c
@@ -0,0 +1,433 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple mailbox driver
+ *
+ * Copyright The Asahi Linux Contributors
+ *
+ * This driver adds support for two mailbox variants (called ASC and M3 by
+ * Apple) found in Apple SoCs such as the M1. It consists of two FIFOs used to
+ * exchange 64+32 bit messages between the main CPU and a co-processor.
+ * Various coprocessors implement different IPC protocols based on these simple
+ * messages and shared memory buffers.
+ *
+ * Both the main CPU and the co-processor see the same set of registers but
+ * the first FIFO (A2I) is always used to transfer messages from the application
+ * processor (us) to the I/O processor and the second one (I2A) for the
+ * other direction.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/pm_runtime.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include "mailbox.h"
+
+#define APPLE_ASC_MBOX_CONTROL_FULL BIT(16)
+#define APPLE_ASC_MBOX_CONTROL_EMPTY BIT(17)
+
+#define APPLE_ASC_MBOX_A2I_CONTROL 0x110
+#define APPLE_ASC_MBOX_A2I_SEND0 0x800
+#define APPLE_ASC_MBOX_A2I_SEND1 0x808
+#define APPLE_ASC_MBOX_A2I_RECV0 0x810
+#define APPLE_ASC_MBOX_A2I_RECV1 0x818
+
+#define APPLE_ASC_MBOX_I2A_CONTROL 0x114
+#define APPLE_ASC_MBOX_I2A_SEND0 0x820
+#define APPLE_ASC_MBOX_I2A_SEND1 0x828
+#define APPLE_ASC_MBOX_I2A_RECV0 0x830
+#define APPLE_ASC_MBOX_I2A_RECV1 0x838
+
+#define APPLE_M3_MBOX_CONTROL_FULL BIT(16)
+#define APPLE_M3_MBOX_CONTROL_EMPTY BIT(17)
+
+#define APPLE_M3_MBOX_A2I_CONTROL 0x50
+#define APPLE_M3_MBOX_A2I_SEND0 0x60
+#define APPLE_M3_MBOX_A2I_SEND1 0x68
+#define APPLE_M3_MBOX_A2I_RECV0 0x70
+#define APPLE_M3_MBOX_A2I_RECV1 0x78
+
+#define APPLE_M3_MBOX_I2A_CONTROL 0x80
+#define APPLE_M3_MBOX_I2A_SEND0 0x90
+#define APPLE_M3_MBOX_I2A_SEND1 0x98
+#define APPLE_M3_MBOX_I2A_RECV0 0xa0
+#define APPLE_M3_MBOX_I2A_RECV1 0xa8
+
+#define APPLE_M3_MBOX_IRQ_ENABLE 0x48
+#define APPLE_M3_MBOX_IRQ_ACK 0x4c
+#define APPLE_M3_MBOX_IRQ_A2I_EMPTY BIT(0)
+#define APPLE_M3_MBOX_IRQ_A2I_NOT_EMPTY BIT(1)
+#define APPLE_M3_MBOX_IRQ_I2A_EMPTY BIT(2)
+#define APPLE_M3_MBOX_IRQ_I2A_NOT_EMPTY BIT(3)
+
+#define APPLE_MBOX_MSG1_OUTCNT GENMASK(56, 52)
+#define APPLE_MBOX_MSG1_INCNT GENMASK(51, 48)
+#define APPLE_MBOX_MSG1_OUTPTR GENMASK(47, 44)
+#define APPLE_MBOX_MSG1_INPTR GENMASK(43, 40)
+#define APPLE_MBOX_MSG1_MSG GENMASK(31, 0)
+
+#define APPLE_MBOX_TX_TIMEOUT 500
+
+struct apple_mbox_hw {
+ unsigned int control_full;
+ unsigned int control_empty;
+
+ unsigned int a2i_control;
+ unsigned int a2i_send0;
+ unsigned int a2i_send1;
+
+ unsigned int i2a_control;
+ unsigned int i2a_recv0;
+ unsigned int i2a_recv1;
+
+ bool has_irq_controls;
+ unsigned int irq_enable;
+ unsigned int irq_ack;
+ unsigned int irq_bit_recv_not_empty;
+ unsigned int irq_bit_send_empty;
+};
+
+int apple_mbox_send(struct apple_mbox *mbox, const struct apple_mbox_msg msg,
+ bool atomic)
+{
+ unsigned long flags;
+ int ret;
+ u32 mbox_ctrl;
+ long t;
+
+ spin_lock_irqsave(&mbox->tx_lock, flags);
+ mbox_ctrl = readl_relaxed(mbox->regs + mbox->hw->a2i_control);
+
+ while (mbox_ctrl & mbox->hw->control_full) {
+ if (atomic) {
+ ret = readl_poll_timeout_atomic(
+ mbox->regs + mbox->hw->a2i_control, mbox_ctrl,
+ !(mbox_ctrl & mbox->hw->control_full), 100,
+ APPLE_MBOX_TX_TIMEOUT * 1000);
+
+ if (ret) {
+ spin_unlock_irqrestore(&mbox->tx_lock, flags);
+ return ret;
+ }
+
+ break;
+ }
+ /*
+ * The interrupt is level triggered and will keep firing as long as the
+ * FIFO is empty. It will also keep firing if the FIFO was empty
+ * at any point in the past until it has been acknowledged at the
+ * mailbox level. By acknowledging it here we can ensure that we will
+ * only get the interrupt once the FIFO has been cleared again.
+ * If the FIFO is already empty before the ack it will fire again
+ * immediately after the ack.
+ */
+ if (mbox->hw->has_irq_controls) {
+ writel_relaxed(mbox->hw->irq_bit_send_empty,
+ mbox->regs + mbox->hw->irq_ack);
+ }
+ enable_irq(mbox->irq_send_empty);
+ reinit_completion(&mbox->tx_empty);
+ spin_unlock_irqrestore(&mbox->tx_lock, flags);
+
+ t = wait_for_completion_interruptible_timeout(
+ &mbox->tx_empty,
+ msecs_to_jiffies(APPLE_MBOX_TX_TIMEOUT));
+ if (t < 0)
+ return t;
+ else if (t == 0)
+ return -ETIMEDOUT;
+
+ spin_lock_irqsave(&mbox->tx_lock, flags);
+ mbox_ctrl = readl_relaxed(mbox->regs + mbox->hw->a2i_control);
+ }
+
+ writeq_relaxed(msg.msg0, mbox->regs + mbox->hw->a2i_send0);
+ writeq_relaxed(FIELD_PREP(APPLE_MBOX_MSG1_MSG, msg.msg1),
+ mbox->regs + mbox->hw->a2i_send1);
+
+ spin_unlock_irqrestore(&mbox->tx_lock, flags);
+
+ return 0;
+}
+EXPORT_SYMBOL(apple_mbox_send);
+
+static irqreturn_t apple_mbox_send_empty_irq(int irq, void *data)
+{
+ struct apple_mbox *mbox = data;
+
+ /*
+ * We don't need to acknowledge the interrupt at the mailbox level
+ * here even if supported by the hardware. It will keep firing but that
+ * doesn't matter since it's disabled at the main interrupt controller.
+ * apple_mbox_send will acknowledge it before enabling
+ * it at the main controller again.
+ */
+ spin_lock(&mbox->tx_lock);
+ disable_irq_nosync(mbox->irq_send_empty);
+ complete(&mbox->tx_empty);
+ spin_unlock(&mbox->tx_lock);
+
+ return IRQ_HANDLED;
+}
+
+static int apple_mbox_poll_locked(struct apple_mbox *mbox)
+{
+ struct apple_mbox_msg msg;
+ int ret = 0;
+
+ u32 mbox_ctrl = readl_relaxed(mbox->regs + mbox->hw->i2a_control);
+
+ while (!(mbox_ctrl & mbox->hw->control_empty)) {
+ msg.msg0 = readq_relaxed(mbox->regs + mbox->hw->i2a_recv0);
+ msg.msg1 = FIELD_GET(
+ APPLE_MBOX_MSG1_MSG,
+ readq_relaxed(mbox->regs + mbox->hw->i2a_recv1));
+
+ mbox->rx(mbox, msg, mbox->cookie);
+ ret++;
+ mbox_ctrl = readl_relaxed(mbox->regs + mbox->hw->i2a_control);
+ }
+
+ /*
+ * The interrupt will keep firing even if there are no more messages
+ * unless we also acknowledge it at the mailbox level here.
+ * There's no race if a message comes in between the check in the while
+ * loop above and the ack below: If a new messages arrives inbetween
+ * those two the interrupt will just fire again immediately after the
+ * ack since it's level triggered.
+ */
+ if (mbox->hw->has_irq_controls) {
+ writel_relaxed(mbox->hw->irq_bit_recv_not_empty,
+ mbox->regs + mbox->hw->irq_ack);
+ }
+
+ return ret;
+}
+
+static irqreturn_t apple_mbox_recv_irq(int irq, void *data)
+{
+ struct apple_mbox *mbox = data;
+
+ spin_lock(&mbox->rx_lock);
+ apple_mbox_poll_locked(mbox);
+ spin_unlock(&mbox->rx_lock);
+
+ return IRQ_HANDLED;
+}
+
+int apple_mbox_poll(struct apple_mbox *mbox)
+{
+ unsigned long flags;
+ int ret;
+
+ spin_lock_irqsave(&mbox->rx_lock, flags);
+ ret = apple_mbox_poll_locked(mbox);
+ spin_unlock_irqrestore(&mbox->rx_lock, flags);
+
+ return ret;
+}
+EXPORT_SYMBOL(apple_mbox_poll);
+
+int apple_mbox_start(struct apple_mbox *mbox)
+{
+ int ret;
+
+ if (mbox->active)
+ return 0;
+
+ ret = pm_runtime_resume_and_get(mbox->dev);
+ if (ret)
+ return ret;
+
+ /*
+ * Only some variants of this mailbox HW provide interrupt control
+ * at the mailbox level. We therefore need to handle enabling/disabling
+ * interrupts at the main interrupt controller anyway for hardware that
+ * doesn't. Just always keep the interrupts we care about enabled at
+ * the mailbox level so that both hardware revisions behave almost
+ * the same.
+ */
+ if (mbox->hw->has_irq_controls) {
+ writel_relaxed(mbox->hw->irq_bit_recv_not_empty |
+ mbox->hw->irq_bit_send_empty,
+ mbox->regs + mbox->hw->irq_enable);
+ }
+
+ enable_irq(mbox->irq_recv_not_empty);
+ mbox->active = true;
+ return 0;
+}
+EXPORT_SYMBOL(apple_mbox_start);
+
+void apple_mbox_stop(struct apple_mbox *mbox)
+{
+ if (!mbox->active)
+ return;
+
+ mbox->active = false;
+ disable_irq(mbox->irq_recv_not_empty);
+ pm_runtime_mark_last_busy(mbox->dev);
+ pm_runtime_put_autosuspend(mbox->dev);
+}
+EXPORT_SYMBOL(apple_mbox_stop);
+
+struct apple_mbox *apple_mbox_get(struct device *dev, int index)
+{
+ struct of_phandle_args args;
+ struct platform_device *pdev;
+ struct apple_mbox *mbox;
+ int ret;
+
+ ret = of_parse_phandle_with_args(dev->of_node, "mboxes", "#mbox-cells",
+ index, &args);
+ if (ret || !args.np)
+ return ERR_PTR(ret);
+
+ pdev = of_find_device_by_node(args.np);
+ of_node_put(args.np);
+
+ if (!pdev)
+ return ERR_PTR(EPROBE_DEFER);
+
+ mbox = platform_get_drvdata(pdev);
+ if (!mbox)
+ return ERR_PTR(EPROBE_DEFER);
+
+ if (!device_link_add(dev, &pdev->dev, DL_FLAG_AUTOREMOVE_CONSUMER))
+ return ERR_PTR(ENODEV);
+
+ return mbox;
+}
+EXPORT_SYMBOL(apple_mbox_get);
+
+struct apple_mbox *apple_mbox_get_byname(struct device *dev, const char *name)
+{
+ int index;
+ index = of_property_match_string(dev->of_node, "mbox-names", name);
+ if (index < 0)
+ return ERR_PTR(index);
+
+ return apple_mbox_get(dev, index);
+}
+EXPORT_SYMBOL(apple_mbox_get_byname);
+
+static int apple_mbox_probe(struct platform_device *pdev)
+{
+ int ret;
+ char *irqname;
+ struct apple_mbox *mbox;
+ struct device *dev = &pdev->dev;
+
+ mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL);
+ if (!mbox)
+ return -ENOMEM;
+
+ mbox->dev = &pdev->dev;
+ mbox->hw = of_device_get_match_data(dev);
+ if (!mbox->hw)
+ return -EINVAL;
+
+ mbox->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(mbox->regs))
+ return PTR_ERR(mbox->regs);
+
+ mbox->irq_recv_not_empty =
+ platform_get_irq_byname(pdev, "recv-not-empty");
+ if (mbox->irq_recv_not_empty < 0)
+ return -ENODEV;
+
+ mbox->irq_send_empty = platform_get_irq_byname(pdev, "send-empty");
+ if (mbox->irq_send_empty < 0)
+ return -ENODEV;
+
+ spin_lock_init(&mbox->rx_lock);
+ spin_lock_init(&mbox->tx_lock);
+ init_completion(&mbox->tx_empty);
+
+ irqname = devm_kasprintf(dev, GFP_KERNEL, "%s-recv", dev_name(dev));
+ if (!irqname)
+ return -ENOMEM;
+
+ ret = devm_request_irq(dev, mbox->irq_recv_not_empty,
+ apple_mbox_recv_irq,
+ IRQF_NO_AUTOEN | IRQF_NO_SUSPEND, irqname, mbox);
+ if (ret)
+ return ret;
+
+ irqname = devm_kasprintf(dev, GFP_KERNEL, "%s-send", dev_name(dev));
+ if (!irqname)
+ return -ENOMEM;
+
+ ret = devm_request_irq(dev, mbox->irq_send_empty,
+ apple_mbox_send_empty_irq,
+ IRQF_NO_AUTOEN | IRQF_NO_SUSPEND, irqname, mbox);
+ if (ret)
+ return ret;
+
+ ret = devm_pm_runtime_enable(dev);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, mbox);
+ return 0;
+}
+
+static const struct apple_mbox_hw apple_mbox_asc_hw = {
+ .control_full = APPLE_ASC_MBOX_CONTROL_FULL,
+ .control_empty = APPLE_ASC_MBOX_CONTROL_EMPTY,
+
+ .a2i_control = APPLE_ASC_MBOX_A2I_CONTROL,
+ .a2i_send0 = APPLE_ASC_MBOX_A2I_SEND0,
+ .a2i_send1 = APPLE_ASC_MBOX_A2I_SEND1,
+
+ .i2a_control = APPLE_ASC_MBOX_I2A_CONTROL,
+ .i2a_recv0 = APPLE_ASC_MBOX_I2A_RECV0,
+ .i2a_recv1 = APPLE_ASC_MBOX_I2A_RECV1,
+
+ .has_irq_controls = false,
+};
+
+static const struct apple_mbox_hw apple_mbox_m3_hw = {
+ .control_full = APPLE_M3_MBOX_CONTROL_FULL,
+ .control_empty = APPLE_M3_MBOX_CONTROL_EMPTY,
+
+ .a2i_control = APPLE_M3_MBOX_A2I_CONTROL,
+ .a2i_send0 = APPLE_M3_MBOX_A2I_SEND0,
+ .a2i_send1 = APPLE_M3_MBOX_A2I_SEND1,
+
+ .i2a_control = APPLE_M3_MBOX_I2A_CONTROL,
+ .i2a_recv0 = APPLE_M3_MBOX_I2A_RECV0,
+ .i2a_recv1 = APPLE_M3_MBOX_I2A_RECV1,
+
+ .has_irq_controls = true,
+ .irq_enable = APPLE_M3_MBOX_IRQ_ENABLE,
+ .irq_ack = APPLE_M3_MBOX_IRQ_ACK,
+ .irq_bit_recv_not_empty = APPLE_M3_MBOX_IRQ_I2A_NOT_EMPTY,
+ .irq_bit_send_empty = APPLE_M3_MBOX_IRQ_A2I_EMPTY,
+};
+
+static const struct of_device_id apple_mbox_of_match[] = {
+ { .compatible = "apple,asc-mailbox-v4", .data = &apple_mbox_asc_hw },
+ { .compatible = "apple,m3-mailbox-v2", .data = &apple_mbox_m3_hw },
+ {}
+};
+MODULE_DEVICE_TABLE(of, apple_mbox_of_match);
+
+static struct platform_driver apple_mbox_driver = {
+ .driver = {
+ .name = "apple-mailbox",
+ .of_match_table = apple_mbox_of_match,
+ },
+ .probe = apple_mbox_probe,
+};
+module_platform_driver(apple_mbox_driver);
+
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_AUTHOR("Sven Peter <sven@svenpeter.dev>");
+MODULE_DESCRIPTION("Apple Mailbox driver");
diff --git a/drivers/soc/apple/mailbox.h b/drivers/soc/apple/mailbox.h
new file mode 100644
index 0000000..f73a891
--- /dev/null
+++ b/drivers/soc/apple/mailbox.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: GPL-2.0-only OR MIT */
+/*
+ * Apple mailbox message format
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+#ifndef _APPLE_MAILBOX_H_
+#define _APPLE_MAILBOX_H_
+
+#include <linux/device.h>
+#include <linux/types.h>
+
+/* encodes a single 96bit message sent over the single channel */
+struct apple_mbox_msg {
+ u64 msg0;
+ u32 msg1;
+};
+
+struct apple_mbox {
+ struct device *dev;
+ void __iomem *regs;
+ const struct apple_mbox_hw *hw;
+ bool active;
+
+ int irq_recv_not_empty;
+ int irq_send_empty;
+
+ spinlock_t rx_lock;
+ spinlock_t tx_lock;
+
+ struct completion tx_empty;
+
+ /** Receive callback for incoming messages */
+ void (*rx)(struct apple_mbox *mbox, struct apple_mbox_msg msg, void *cookie);
+ void *cookie;
+};
+
+struct apple_mbox *apple_mbox_get(struct device *dev, int index);
+struct apple_mbox *apple_mbox_get_byname(struct device *dev, const char *name);
+
+int apple_mbox_start(struct apple_mbox *mbox);
+void apple_mbox_stop(struct apple_mbox *mbox);
+int apple_mbox_poll(struct apple_mbox *mbox);
+int apple_mbox_send(struct apple_mbox *mbox, struct apple_mbox_msg msg,
+ bool atomic);
+
+#endif
diff --git a/drivers/soc/apple/rtkit-helper.c b/drivers/soc/apple/rtkit-helper.c
new file mode 100644
index 0000000..46bf0f0
--- /dev/null
+++ b/drivers/soc/apple/rtkit-helper.c
@@ -0,0 +1,152 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple Generic RTKit helper coprocessor
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/soc/apple/rtkit.h>
+
+#define APPLE_ASC_CPU_CONTROL 0x44
+#define APPLE_ASC_CPU_CONTROL_RUN BIT(4)
+
+struct apple_rtkit_helper {
+ struct device *dev;
+ struct apple_rtkit *rtk;
+
+ void __iomem *asc_base;
+
+ struct resource *sram;
+ void __iomem *sram_base;
+};
+
+static int apple_rtkit_helper_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr)
+{
+ struct apple_rtkit_helper *helper = cookie;
+ struct resource res = {
+ .start = bfr->iova,
+ .end = bfr->iova + bfr->size - 1,
+ .name = "rtkit_map",
+ };
+
+ if (!bfr->iova) {
+ bfr->buffer = dma_alloc_coherent(helper->dev, bfr->size,
+ &bfr->iova, GFP_KERNEL);
+ if (!bfr->buffer)
+ return -ENOMEM;
+ return 0;
+ }
+
+ if (!helper->sram) {
+ dev_err(helper->dev,
+ "RTKit buffer request with no SRAM region: %pR", &res);
+ return -EFAULT;
+ }
+
+ res.flags = helper->sram->flags;
+
+ if (res.end < res.start || !resource_contains(helper->sram, &res)) {
+ dev_err(helper->dev,
+ "RTKit buffer request outside SRAM region: %pR", &res);
+ return -EFAULT;
+ }
+
+ bfr->iomem = helper->sram_base + (res.start - helper->sram->start);
+ bfr->is_mapped = true;
+
+ return 0;
+}
+
+static void apple_rtkit_helper_shmem_destroy(void *cookie, struct apple_rtkit_shmem *bfr)
+{
+ // no-op
+}
+
+static const struct apple_rtkit_ops apple_rtkit_helper_ops = {
+ .shmem_setup = apple_rtkit_helper_shmem_setup,
+ .shmem_destroy = apple_rtkit_helper_shmem_destroy,
+};
+
+static int apple_rtkit_helper_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct apple_rtkit_helper *helper;
+ int ret;
+
+ /* 44 bits for addresses in standard RTKit requests */
+ ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(44));
+ if (ret)
+ return ret;
+
+ helper = devm_kzalloc(dev, sizeof(*helper), GFP_KERNEL);
+ if (!helper)
+ return -ENOMEM;
+
+ helper->dev = dev;
+ platform_set_drvdata(pdev, helper);
+
+ helper->asc_base = devm_platform_ioremap_resource_byname(pdev, "asc");
+ if (IS_ERR(helper->asc_base))
+ return PTR_ERR(helper->asc_base);
+
+ helper->sram = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sram");
+ if (helper->sram) {
+ helper->sram_base = devm_ioremap_resource(dev, helper->sram);
+ if (IS_ERR(helper->sram_base))
+ return dev_err_probe(dev, PTR_ERR(helper->sram_base),
+ "Failed to map SRAM region");
+ }
+
+ helper->rtk =
+ devm_apple_rtkit_init(dev, helper, NULL, 0, &apple_rtkit_helper_ops);
+ if (IS_ERR(helper->rtk))
+ return dev_err_probe(dev, PTR_ERR(helper->rtk),
+ "Failed to intialize RTKit");
+
+ writel_relaxed(APPLE_ASC_CPU_CONTROL_RUN,
+ helper->asc_base + APPLE_ASC_CPU_CONTROL);
+
+ /* Works for both wake and boot */
+ ret = apple_rtkit_wake(helper->rtk);
+ if (ret != 0)
+ return dev_err_probe(dev, ret, "Failed to wake up coprocessor");
+
+ return 0;
+}
+
+static int apple_rtkit_helper_remove(struct platform_device *pdev)
+{
+ struct apple_rtkit_helper *helper = platform_get_drvdata(pdev);
+
+ if (apple_rtkit_is_running(helper->rtk))
+ apple_rtkit_quiesce(helper->rtk);
+
+ writel_relaxed(0, helper->asc_base + APPLE_ASC_CPU_CONTROL);
+
+ return 0;
+}
+
+static const struct of_device_id apple_rtkit_helper_of_match[] = {
+ { .compatible = "apple,rtk-helper-asc4" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, apple_rtkit_helper_of_match);
+
+static struct platform_driver apple_rtkit_helper_driver = {
+ .driver = {
+ .name = "rtkit-helper",
+ .of_match_table = apple_rtkit_helper_of_match,
+ },
+ .probe = apple_rtkit_helper_probe,
+ .remove = apple_rtkit_helper_remove,
+};
+module_platform_driver(apple_rtkit_helper_driver);
+
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_DESCRIPTION("Apple RTKit helper driver");
diff --git a/drivers/soc/apple/rtkit-internal.h b/drivers/soc/apple/rtkit-internal.h
index 24bd619..b8d5244 100644
--- a/drivers/soc/apple/rtkit-internal.h
+++ b/drivers/soc/apple/rtkit-internal.h
@@ -7,18 +7,17 @@
#ifndef _APPLE_RTKIT_INTERAL_H
#define _APPLE_RTKIT_INTERAL_H
-#include <linux/apple-mailbox.h>
#include <linux/bitfield.h>
#include <linux/bitmap.h>
#include <linux/completion.h>
#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <linux/kernel.h>
-#include <linux/mailbox_client.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/soc/apple/rtkit.h>
#include <linux/workqueue.h>
+#include "mailbox.h"
#define APPLE_RTKIT_APP_ENDPOINT_START 0x20
#define APPLE_RTKIT_MAX_ENDPOINTS 0x100
@@ -28,10 +27,7 @@ struct apple_rtkit {
const struct apple_rtkit_ops *ops;
struct device *dev;
- const char *mbox_name;
- int mbox_idx;
- struct mbox_client mbox_cl;
- struct mbox_chan *mbox_chan;
+ struct apple_mbox *mbox;
struct completion epmap_completion;
struct completion iop_pwr_ack_completion;
@@ -48,6 +44,7 @@ struct apple_rtkit {
struct apple_rtkit_shmem ioreport_buffer;
struct apple_rtkit_shmem crashlog_buffer;
+ struct apple_rtkit_shmem oslog_buffer;
struct apple_rtkit_shmem syslog_buffer;
char *syslog_msg_buffer;
diff --git a/drivers/soc/apple/rtkit.c b/drivers/soc/apple/rtkit.c
index d9f19dc..daed4b5 100644
--- a/drivers/soc/apple/rtkit.c
+++ b/drivers/soc/apple/rtkit.c
@@ -12,6 +12,7 @@ enum {
APPLE_RTKIT_PWR_STATE_IDLE = 0x201, /* sleeping, retain state */
APPLE_RTKIT_PWR_STATE_QUIESCED = 0x10, /* running but no communication */
APPLE_RTKIT_PWR_STATE_ON = 0x20, /* normal operating state */
+ APPLE_RTKIT_PWR_STATE_INIT = 0x220, /* init after starting the coproc */
};
enum {
@@ -66,17 +67,13 @@ enum {
#define APPLE_RTKIT_SYSLOG_MSG_SIZE GENMASK_ULL(31, 24)
#define APPLE_RTKIT_OSLOG_TYPE GENMASK_ULL(63, 56)
-#define APPLE_RTKIT_OSLOG_INIT 1
-#define APPLE_RTKIT_OSLOG_ACK 3
+#define APPLE_RTKIT_OSLOG_BUFFER_REQUEST 1
+#define APPLE_RTKIT_OSLOG_SIZE GENMASK_ULL(55, 36)
+#define APPLE_RTKIT_OSLOG_IOVA GENMASK_ULL(35, 0)
#define APPLE_RTKIT_MIN_SUPPORTED_VERSION 11
#define APPLE_RTKIT_MAX_SUPPORTED_VERSION 12
-struct apple_rtkit_msg {
- struct completion *completion;
- struct apple_mbox_msg mbox_msg;
-};
-
struct apple_rtkit_rx_work {
struct apple_rtkit *rtk;
u8 ep;
@@ -102,12 +99,20 @@ bool apple_rtkit_is_crashed(struct apple_rtkit *rtk)
}
EXPORT_SYMBOL_GPL(apple_rtkit_is_crashed);
-static void apple_rtkit_management_send(struct apple_rtkit *rtk, u8 type,
+static int apple_rtkit_management_send(struct apple_rtkit *rtk, u8 type,
u64 msg)
{
+ int ret;
+
msg &= ~APPLE_RTKIT_MGMT_TYPE;
msg |= FIELD_PREP(APPLE_RTKIT_MGMT_TYPE, type);
- apple_rtkit_send_message(rtk, APPLE_RTKIT_EP_MGMT, msg, NULL, false);
+ ret = apple_rtkit_send_message(rtk, APPLE_RTKIT_EP_MGMT, msg, NULL, false);
+
+ if (ret) {
+ dev_err(rtk->dev, "RTKit: Failed to send management message: %d\n", ret);
+ }
+
+ return ret;
}
static void apple_rtkit_management_rx_hello(struct apple_rtkit *rtk, u64 msg)
@@ -256,15 +261,20 @@ static int apple_rtkit_common_rx_get_buffer(struct apple_rtkit *rtk,
struct apple_rtkit_shmem *buffer,
u8 ep, u64 msg)
{
- size_t n_4kpages = FIELD_GET(APPLE_RTKIT_BUFFER_REQUEST_SIZE, msg);
u64 reply;
int err;
+ if (ep == APPLE_RTKIT_EP_OSLOG) {
+ buffer->size = FIELD_GET(APPLE_RTKIT_OSLOG_SIZE, msg);
+ buffer->iova = FIELD_GET(APPLE_RTKIT_OSLOG_IOVA, msg) << 12;
+ } else {
+ buffer->size = FIELD_GET(APPLE_RTKIT_BUFFER_REQUEST_SIZE, msg) << 12;
+ buffer->iova = FIELD_GET(APPLE_RTKIT_BUFFER_REQUEST_IOVA, msg);
+ }
+
buffer->buffer = NULL;
buffer->iomem = NULL;
buffer->is_mapped = false;
- buffer->iova = FIELD_GET(APPLE_RTKIT_BUFFER_REQUEST_IOVA, msg);
- buffer->size = n_4kpages << 12;
dev_dbg(rtk->dev, "RTKit: buffer request for 0x%zx bytes at %pad\n",
buffer->size, &buffer->iova);
@@ -289,17 +299,30 @@ static int apple_rtkit_common_rx_get_buffer(struct apple_rtkit *rtk,
}
if (!buffer->is_mapped) {
- reply = FIELD_PREP(APPLE_RTKIT_SYSLOG_TYPE,
- APPLE_RTKIT_BUFFER_REQUEST);
- reply |= FIELD_PREP(APPLE_RTKIT_BUFFER_REQUEST_SIZE, n_4kpages);
- reply |= FIELD_PREP(APPLE_RTKIT_BUFFER_REQUEST_IOVA,
- buffer->iova);
+ /* oslog uses different fields */
+ if (ep == APPLE_RTKIT_EP_OSLOG) {
+ reply = FIELD_PREP(APPLE_RTKIT_OSLOG_TYPE,
+ APPLE_RTKIT_OSLOG_BUFFER_REQUEST);
+ reply |= FIELD_PREP(APPLE_RTKIT_OSLOG_SIZE, buffer->size);
+ reply |= FIELD_PREP(APPLE_RTKIT_OSLOG_IOVA,
+ buffer->iova >> 12);
+ } else {
+ reply = FIELD_PREP(APPLE_RTKIT_SYSLOG_TYPE,
+ APPLE_RTKIT_BUFFER_REQUEST);
+ reply |= FIELD_PREP(APPLE_RTKIT_BUFFER_REQUEST_SIZE,
+ buffer->size >> 12);
+ reply |= FIELD_PREP(APPLE_RTKIT_BUFFER_REQUEST_IOVA,
+ buffer->iova);
+ }
apple_rtkit_send_message(rtk, ep, reply, NULL, false);
}
return 0;
error:
+ dev_err(rtk->dev, "RTKit: failed buffer request for 0x%zx bytes (%d)\n",
+ buffer->size, err);
+
buffer->buffer = NULL;
buffer->iomem = NULL;
buffer->iova = 0;
@@ -487,25 +510,18 @@ static void apple_rtkit_syslog_rx(struct apple_rtkit *rtk, u64 msg)
}
}
-static void apple_rtkit_oslog_rx_init(struct apple_rtkit *rtk, u64 msg)
-{
- u64 ack;
-
- dev_dbg(rtk->dev, "RTKit: oslog init: msg: 0x%llx\n", msg);
- ack = FIELD_PREP(APPLE_RTKIT_OSLOG_TYPE, APPLE_RTKIT_OSLOG_ACK);
- apple_rtkit_send_message(rtk, APPLE_RTKIT_EP_OSLOG, ack, NULL, false);
-}
-
static void apple_rtkit_oslog_rx(struct apple_rtkit *rtk, u64 msg)
{
u8 type = FIELD_GET(APPLE_RTKIT_OSLOG_TYPE, msg);
switch (type) {
- case APPLE_RTKIT_OSLOG_INIT:
- apple_rtkit_oslog_rx_init(rtk, msg);
+ case APPLE_RTKIT_OSLOG_BUFFER_REQUEST:
+ apple_rtkit_common_rx_get_buffer(rtk, &rtk->oslog_buffer,
+ APPLE_RTKIT_EP_OSLOG, msg);
break;
default:
- dev_warn(rtk->dev, "RTKit: Unknown oslog message: %llx\n", msg);
+ dev_warn(rtk->dev, "RTKit: Unknown oslog message: %llx\n",
+ msg);
}
}
@@ -550,12 +566,12 @@ static void apple_rtkit_rx_work(struct work_struct *work)
kfree(rtk_work);
}
-static void apple_rtkit_rx(struct mbox_client *cl, void *mssg)
+static void apple_rtkit_rx(struct apple_mbox *mbox, struct apple_mbox_msg msg,
+ void *cookie)
{
- struct apple_rtkit *rtk = container_of(cl, struct apple_rtkit, mbox_cl);
- struct apple_mbox_msg *msg = mssg;
+ struct apple_rtkit *rtk = cookie;
struct apple_rtkit_rx_work *work;
- u8 ep = msg->msg1;
+ u8 ep = msg.msg1;
/*
* The message was read from a MMIO FIFO and we have to make
@@ -571,7 +587,7 @@ static void apple_rtkit_rx(struct mbox_client *cl, void *mssg)
if (ep >= APPLE_RTKIT_APP_ENDPOINT_START &&
rtk->ops->recv_message_early &&
- rtk->ops->recv_message_early(rtk->cookie, ep, msg->msg0))
+ rtk->ops->recv_message_early(rtk->cookie, ep, msg.msg0))
return;
work = kzalloc(sizeof(*work), GFP_ATOMIC);
@@ -580,49 +596,31 @@ static void apple_rtkit_rx(struct mbox_client *cl, void *mssg)
work->rtk = rtk;
work->ep = ep;
- work->msg = msg->msg0;
+ work->msg = msg.msg0;
INIT_WORK(&work->work, apple_rtkit_rx_work);
queue_work(rtk->wq, &work->work);
}
-static void apple_rtkit_tx_done(struct mbox_client *cl, void *mssg, int r)
-{
- struct apple_rtkit_msg *msg =
- container_of(mssg, struct apple_rtkit_msg, mbox_msg);
-
- if (r == -ETIME)
- return;
-
- if (msg->completion)
- complete(msg->completion);
- kfree(msg);
-}
-
int apple_rtkit_send_message(struct apple_rtkit *rtk, u8 ep, u64 message,
struct completion *completion, bool atomic)
{
- struct apple_rtkit_msg *msg;
- int ret;
- gfp_t flags;
+ struct apple_mbox_msg msg = {
+ .msg0 = message,
+ .msg1 = ep,
+ };
- if (rtk->crashed)
+ if (rtk->crashed) {
+ dev_warn(rtk->dev,
+ "RTKit: Device is crashed, cannot send message\n");
return -EINVAL;
+ }
+
if (ep >= APPLE_RTKIT_APP_ENDPOINT_START &&
- !apple_rtkit_is_running(rtk))
+ !apple_rtkit_is_running(rtk)) {
+ dev_warn(rtk->dev,
+ "RTKit: Endpoint 0x%02x is not running, cannot send message\n", ep);
return -EINVAL;
-
- if (atomic)
- flags = GFP_ATOMIC;
- else
- flags = GFP_KERNEL;
-
- msg = kzalloc(sizeof(*msg), flags);
- if (!msg)
- return -ENOMEM;
-
- msg->mbox_msg.msg0 = message;
- msg->mbox_msg.msg1 = ep;
- msg->completion = completion;
+ }
/*
* The message will be sent with a MMIO write. We need the barrier
@@ -631,51 +629,13 @@ int apple_rtkit_send_message(struct apple_rtkit *rtk, u8 ep, u64 message,
*/
dma_wmb();
- ret = mbox_send_message(rtk->mbox_chan, &msg->mbox_msg);
- if (ret < 0) {
- kfree(msg);
- return ret;
- }
-
- return 0;
+ return apple_mbox_send(rtk->mbox, msg, atomic);
}
EXPORT_SYMBOL_GPL(apple_rtkit_send_message);
-int apple_rtkit_send_message_wait(struct apple_rtkit *rtk, u8 ep, u64 message,
- unsigned long timeout, bool atomic)
-{
- DECLARE_COMPLETION_ONSTACK(completion);
- int ret;
- long t;
-
- ret = apple_rtkit_send_message(rtk, ep, message, &completion, atomic);
- if (ret < 0)
- return ret;
-
- if (atomic) {
- ret = mbox_flush(rtk->mbox_chan, timeout);
- if (ret < 0)
- return ret;
-
- if (try_wait_for_completion(&completion))
- return 0;
-
- return -ETIME;
- } else {
- t = wait_for_completion_interruptible_timeout(
- &completion, msecs_to_jiffies(timeout));
- if (t < 0)
- return t;
- else if (t == 0)
- return -ETIME;
- return 0;
- }
-}
-EXPORT_SYMBOL_GPL(apple_rtkit_send_message_wait);
-
int apple_rtkit_poll(struct apple_rtkit *rtk)
{
- return mbox_client_peek_data(rtk->mbox_chan);
+ return apple_mbox_poll(rtk->mbox);
}
EXPORT_SYMBOL_GPL(apple_rtkit_poll);
@@ -697,20 +657,6 @@ int apple_rtkit_start_ep(struct apple_rtkit *rtk, u8 endpoint)
}
EXPORT_SYMBOL_GPL(apple_rtkit_start_ep);
-static int apple_rtkit_request_mbox_chan(struct apple_rtkit *rtk)
-{
- if (rtk->mbox_name)
- rtk->mbox_chan = mbox_request_channel_byname(&rtk->mbox_cl,
- rtk->mbox_name);
- else
- rtk->mbox_chan =
- mbox_request_channel(&rtk->mbox_cl, rtk->mbox_idx);
-
- if (IS_ERR(rtk->mbox_chan))
- return PTR_ERR(rtk->mbox_chan);
- return 0;
-}
-
struct apple_rtkit *apple_rtkit_init(struct device *dev, void *cookie,
const char *mbox_name, int mbox_idx,
const struct apple_rtkit_ops *ops)
@@ -736,13 +682,18 @@ struct apple_rtkit *apple_rtkit_init(struct device *dev, void *cookie,
bitmap_zero(rtk->endpoints, APPLE_RTKIT_MAX_ENDPOINTS);
set_bit(APPLE_RTKIT_EP_MGMT, rtk->endpoints);
- rtk->mbox_name = mbox_name;
- rtk->mbox_idx = mbox_idx;
- rtk->mbox_cl.dev = dev;
- rtk->mbox_cl.tx_block = false;
- rtk->mbox_cl.knows_txdone = false;
- rtk->mbox_cl.rx_callback = &apple_rtkit_rx;
- rtk->mbox_cl.tx_done = &apple_rtkit_tx_done;
+ if (mbox_name)
+ rtk->mbox = apple_mbox_get_byname(dev, mbox_name);
+ else
+ rtk->mbox = apple_mbox_get(dev, mbox_idx);
+
+ if (IS_ERR(rtk->mbox)) {
+ ret = PTR_ERR(rtk->mbox);
+ goto free_rtk;
+ }
+
+ rtk->mbox->rx = apple_rtkit_rx;
+ rtk->mbox->cookie = rtk;
rtk->wq = alloc_ordered_workqueue("rtkit-%s", WQ_MEM_RECLAIM,
dev_name(rtk->dev));
@@ -751,7 +702,7 @@ struct apple_rtkit *apple_rtkit_init(struct device *dev, void *cookie,
goto free_rtk;
}
- ret = apple_rtkit_request_mbox_chan(rtk);
+ ret = apple_mbox_start(rtk->mbox);
if (ret)
goto destroy_wq;
@@ -782,11 +733,12 @@ static int apple_rtkit_wait_for_completion(struct completion *c)
int apple_rtkit_reinit(struct apple_rtkit *rtk)
{
/* make sure we don't handle any messages while reinitializing */
- mbox_free_channel(rtk->mbox_chan);
+ apple_mbox_stop(rtk->mbox);
flush_workqueue(rtk->wq);
apple_rtkit_free_buffer(rtk, &rtk->ioreport_buffer);
apple_rtkit_free_buffer(rtk, &rtk->crashlog_buffer);
+ apple_rtkit_free_buffer(rtk, &rtk->oslog_buffer);
apple_rtkit_free_buffer(rtk, &rtk->syslog_buffer);
kfree(rtk->syslog_msg_buffer);
@@ -806,7 +758,7 @@ int apple_rtkit_reinit(struct apple_rtkit *rtk)
rtk->iop_power_state = APPLE_RTKIT_PWR_STATE_OFF;
rtk->ap_power_state = APPLE_RTKIT_PWR_STATE_OFF;
- return apple_rtkit_request_mbox_chan(rtk);
+ return apple_mbox_start(rtk->mbox);
}
EXPORT_SYMBOL_GPL(apple_rtkit_reinit);
@@ -819,8 +771,10 @@ static int apple_rtkit_set_ap_power_state(struct apple_rtkit *rtk,
reinit_completion(&rtk->ap_pwr_ack_completion);
msg = FIELD_PREP(APPLE_RTKIT_MGMT_PWR_STATE, state);
- apple_rtkit_management_send(rtk, APPLE_RTKIT_MGMT_SET_AP_PWR_STATE,
- msg);
+ ret = apple_rtkit_management_send(rtk, APPLE_RTKIT_MGMT_SET_AP_PWR_STATE,
+ msg);
+ if (ret)
+ return ret;
ret = apple_rtkit_wait_for_completion(&rtk->ap_pwr_ack_completion);
if (ret)
@@ -840,8 +794,10 @@ static int apple_rtkit_set_iop_power_state(struct apple_rtkit *rtk,
reinit_completion(&rtk->iop_pwr_ack_completion);
msg = FIELD_PREP(APPLE_RTKIT_MGMT_PWR_STATE, state);
- apple_rtkit_management_send(rtk, APPLE_RTKIT_MGMT_SET_IOP_PWR_STATE,
- msg);
+ ret = apple_rtkit_management_send(rtk, APPLE_RTKIT_MGMT_SET_IOP_PWR_STATE,
+ msg);
+ if (ret)
+ return ret;
ret = apple_rtkit_wait_for_completion(&rtk->iop_pwr_ack_completion);
if (ret)
@@ -942,6 +898,7 @@ EXPORT_SYMBOL_GPL(apple_rtkit_quiesce);
int apple_rtkit_wake(struct apple_rtkit *rtk)
{
u64 msg;
+ int ret;
if (apple_rtkit_is_running(rtk))
return -EINVAL;
@@ -952,9 +909,11 @@ int apple_rtkit_wake(struct apple_rtkit *rtk)
* Use open-coded apple_rtkit_set_iop_power_state since apple_rtkit_boot
* will wait for the completion anyway.
*/
- msg = FIELD_PREP(APPLE_RTKIT_MGMT_PWR_STATE, APPLE_RTKIT_PWR_STATE_ON);
- apple_rtkit_management_send(rtk, APPLE_RTKIT_MGMT_SET_IOP_PWR_STATE,
- msg);
+ msg = FIELD_PREP(APPLE_RTKIT_MGMT_PWR_STATE, APPLE_RTKIT_PWR_STATE_INIT);
+ ret = apple_rtkit_management_send(rtk, APPLE_RTKIT_MGMT_SET_IOP_PWR_STATE,
+ msg);
+ if (ret)
+ return ret;
return apple_rtkit_boot(rtk);
}
@@ -962,11 +921,12 @@ EXPORT_SYMBOL_GPL(apple_rtkit_wake);
void apple_rtkit_free(struct apple_rtkit *rtk)
{
- mbox_free_channel(rtk->mbox_chan);
+ apple_mbox_stop(rtk->mbox);
destroy_workqueue(rtk->wq);
apple_rtkit_free_buffer(rtk, &rtk->ioreport_buffer);
apple_rtkit_free_buffer(rtk, &rtk->crashlog_buffer);
+ apple_rtkit_free_buffer(rtk, &rtk->oslog_buffer);
apple_rtkit_free_buffer(rtk, &rtk->syslog_buffer);
kfree(rtk->syslog_msg_buffer);
@@ -998,6 +958,12 @@ struct apple_rtkit *devm_apple_rtkit_init(struct device *dev, void *cookie,
}
EXPORT_SYMBOL_GPL(devm_apple_rtkit_init);
+void devm_apple_rtkit_free(struct device *dev, struct apple_rtkit *rtk)
+{
+ devm_release_action(dev, apple_rtkit_free_wrapper, rtk);
+}
+EXPORT_SYMBOL_GPL(devm_apple_rtkit_free);
+
MODULE_LICENSE("Dual MIT/GPL");
MODULE_AUTHOR("Sven Peter <sven@svenpeter.dev>");
MODULE_DESCRIPTION("Apple RTKit driver");
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 70c9dd6..d5d9606 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -86,6 +86,14 @@
This enables master mode support for the SPIFC (SPI flash
controller) available in Amlogic A1 (A113L SoC).
+config SPI_APPLE
+ tristate "Apple SoC SPI Controller platform driver"
+ depends on ARCH_APPLE || COMPILE_TEST
+ help
+ This enables support for the SPI controller present on
+ many Apple SoCs, including the t8103 (M1) and t600x
+ (M1 Pro/Max).
+
config SPI_AR934X
tristate "Qualcomm Atheros AR934X/QCA95XX SPI controller driver"
depends on ATH79 || COMPILE_TEST
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 4ff8d72..5785da7 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -18,6 +18,7 @@
obj-$(CONFIG_SPI_ALTERA_CORE) += spi-altera-core.o
obj-$(CONFIG_SPI_ALTERA_DFL) += spi-altera-dfl.o
obj-$(CONFIG_SPI_AMLOGIC_SPIFC_A1) += spi-amlogic-spifc-a1.o
+obj-$(CONFIG_SPI_APPLE) += spi-apple.o
obj-$(CONFIG_SPI_AR934X) += spi-ar934x.o
obj-$(CONFIG_SPI_ARMADA_3700) += spi-armada-3700.o
obj-$(CONFIG_SPI_ASPEED_SMC) += spi-aspeed-smc.o
diff --git a/drivers/spi/spi-apple.c b/drivers/spi/spi-apple.c
new file mode 100644
index 0000000..c483ad3
--- /dev/null
+++ b/drivers/spi/spi-apple.c
@@ -0,0 +1,544 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Apple SoC SPI device driver
+ *
+ * Copyright The Asahi Linux Contributors
+ *
+ * Based on spi-sifive.c, Copyright 2018 SiFive, Inc.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/spi/spi.h>
+
+#define APPLE_SPI_CTRL 0x000
+#define APPLE_SPI_CTRL_RUN BIT(0)
+#define APPLE_SPI_CTRL_TX_RESET BIT(2)
+#define APPLE_SPI_CTRL_RX_RESET BIT(3)
+
+#define APPLE_SPI_CFG 0x004
+#define APPLE_SPI_CFG_CPHA BIT(1)
+#define APPLE_SPI_CFG_CPOL BIT(2)
+#define APPLE_SPI_CFG_MODE GENMASK(6, 5)
+#define APPLE_SPI_CFG_MODE_POLLED 0
+#define APPLE_SPI_CFG_MODE_IRQ 1
+#define APPLE_SPI_CFG_MODE_DMA 2
+#define APPLE_SPI_CFG_IE_RXCOMPLETE BIT(7)
+#define APPLE_SPI_CFG_IE_TXRXTHRESH BIT(8)
+#define APPLE_SPI_CFG_LSB_FIRST BIT(13)
+#define APPLE_SPI_CFG_WORD_SIZE GENMASK(16, 15)
+#define APPLE_SPI_CFG_WORD_SIZE_8B 0
+#define APPLE_SPI_CFG_WORD_SIZE_16B 1
+#define APPLE_SPI_CFG_WORD_SIZE_32B 2
+#define APPLE_SPI_CFG_FIFO_THRESH GENMASK(18, 17)
+#define APPLE_SPI_CFG_FIFO_THRESH_8B 0
+#define APPLE_SPI_CFG_FIFO_THRESH_4B 1
+#define APPLE_SPI_CFG_FIFO_THRESH_1B 2
+#define APPLE_SPI_CFG_IE_TXCOMPLETE BIT(21)
+
+#define APPLE_SPI_STATUS 0x008
+#define APPLE_SPI_STATUS_RXCOMPLETE BIT(0)
+#define APPLE_SPI_STATUS_TXRXTHRESH BIT(1)
+#define APPLE_SPI_STATUS_TXCOMPLETE BIT(2)
+
+#define APPLE_SPI_PIN 0x00c
+#define APPLE_SPI_PIN_KEEP_MOSI BIT(0)
+#define APPLE_SPI_PIN_CS BIT(1)
+
+#define APPLE_SPI_TXDATA 0x010
+#define APPLE_SPI_RXDATA 0x020
+#define APPLE_SPI_CLKDIV 0x030
+#define APPLE_SPI_CLKDIV_MAX 0x7ff
+#define APPLE_SPI_RXCNT 0x034
+#define APPLE_SPI_WORD_DELAY 0x038
+#define APPLE_SPI_TXCNT 0x04c
+
+#define APPLE_SPI_FIFOSTAT 0x10c
+#define APPLE_SPI_FIFOSTAT_TXFULL BIT(4)
+#define APPLE_SPI_FIFOSTAT_LEVEL_TX GENMASK(15, 8)
+#define APPLE_SPI_FIFOSTAT_RXEMPTY BIT(20)
+#define APPLE_SPI_FIFOSTAT_LEVEL_RX GENMASK(31, 24)
+
+#define APPLE_SPI_IE_XFER 0x130
+#define APPLE_SPI_IF_XFER 0x134
+#define APPLE_SPI_XFER_RXCOMPLETE BIT(0)
+#define APPLE_SPI_XFER_TXCOMPLETE BIT(1)
+
+#define APPLE_SPI_IE_FIFO 0x138
+#define APPLE_SPI_IF_FIFO 0x13c
+#define APPLE_SPI_FIFO_RXTHRESH BIT(4)
+#define APPLE_SPI_FIFO_TXTHRESH BIT(5)
+#define APPLE_SPI_FIFO_RXFULL BIT(8)
+#define APPLE_SPI_FIFO_TXEMPTY BIT(9)
+#define APPLE_SPI_FIFO_RXUNDERRUN BIT(16)
+#define APPLE_SPI_FIFO_TXOVERFLOW BIT(17)
+
+#define APPLE_SPI_SHIFTCFG 0x150
+#define APPLE_SPI_SHIFTCFG_CLK_ENABLE BIT(0)
+#define APPLE_SPI_SHIFTCFG_CS_ENABLE BIT(1)
+#define APPLE_SPI_SHIFTCFG_AND_CLK_DATA BIT(8)
+#define APPLE_SPI_SHIFTCFG_CS_AS_DATA BIT(9)
+#define APPLE_SPI_SHIFTCFG_TX_ENABLE BIT(10)
+#define APPLE_SPI_SHIFTCFG_RX_ENABLE BIT(11)
+#define APPLE_SPI_SHIFTCFG_BITS GENMASK(21, 16)
+#define APPLE_SPI_SHIFTCFG_OVERRIDE_CS BIT(24)
+
+#define APPLE_SPI_PINCFG 0x154
+#define APPLE_SPI_PINCFG_KEEP_CLK BIT(0)
+#define APPLE_SPI_PINCFG_KEEP_CS BIT(1)
+#define APPLE_SPI_PINCFG_KEEP_MOSI BIT(2)
+#define APPLE_SPI_PINCFG_CLK_IDLE_VAL BIT(8)
+#define APPLE_SPI_PINCFG_CS_IDLE_VAL BIT(9)
+#define APPLE_SPI_PINCFG_MOSI_IDLE_VAL BIT(10)
+
+#define APPLE_SPI_DELAY_PRE 0x160
+#define APPLE_SPI_DELAY_POST 0x168
+#define APPLE_SPI_DELAY_ENABLE BIT(0)
+#define APPLE_SPI_DELAY_NO_INTERBYTE BIT(1)
+#define APPLE_SPI_DELAY_SET_SCK BIT(4)
+#define APPLE_SPI_DELAY_SET_MOSI BIT(6)
+#define APPLE_SPI_DELAY_SCK_VAL BIT(8)
+#define APPLE_SPI_DELAY_MOSI_VAL BIT(12)
+
+#define APPLE_SPI_FIFO_DEPTH 16
+
+/*
+ * The slowest refclock available is 24MHz, the highest divider is 0x7ff,
+ * the largest word size is 32 bits, the FIFO depth is 16, the maximum
+ * intra-word delay is 0xffff refclocks. So the maximum time a transfer
+ * cycle can take is:
+ *
+ * (0x7ff * 32 + 0xffff) * 16 / 24e6 Hz ~= 87ms
+ *
+ * Double it and round it up to 200ms for good measure.
+ */
+#define APPLE_SPI_TIMEOUT_MS 200
+
+struct apple_spi {
+ void __iomem *regs; /* MMIO register address */
+ struct clk *clk; /* bus clock */
+ struct completion done; /* wake-up from interrupt */
+};
+
+static inline void reg_write(struct apple_spi *spi, int offset, u32 value)
+{
+ writel_relaxed(value, spi->regs + offset);
+}
+
+static inline u32 reg_read(struct apple_spi *spi, int offset)
+{
+ return readl_relaxed(spi->regs + offset);
+}
+
+static inline void reg_mask(struct apple_spi *spi, int offset, u32 clear, u32 set)
+{
+ u32 val = reg_read(spi, offset);
+
+ val &= ~clear;
+ val |= set;
+ reg_write(spi, offset, val);
+}
+
+static void apple_spi_init(struct apple_spi *spi)
+{
+ /* Set CS high (inactive) and disable override and auto-CS */
+ reg_write(spi, APPLE_SPI_PIN, APPLE_SPI_PIN_CS);
+ reg_mask(spi, APPLE_SPI_SHIFTCFG, APPLE_SPI_SHIFTCFG_OVERRIDE_CS, 0);
+ reg_mask(spi, APPLE_SPI_PINCFG, APPLE_SPI_PINCFG_CS_IDLE_VAL, APPLE_SPI_PINCFG_KEEP_CS);
+
+ /* Reset FIFOs */
+ reg_write(spi, APPLE_SPI_CTRL, APPLE_SPI_CTRL_RX_RESET | APPLE_SPI_CTRL_TX_RESET);
+
+ /* Configure defaults */
+ reg_write(spi, APPLE_SPI_CFG,
+ FIELD_PREP(APPLE_SPI_CFG_FIFO_THRESH, APPLE_SPI_CFG_FIFO_THRESH_8B) |
+ FIELD_PREP(APPLE_SPI_CFG_MODE, APPLE_SPI_CFG_MODE_IRQ) |
+ FIELD_PREP(APPLE_SPI_CFG_WORD_SIZE, APPLE_SPI_CFG_WORD_SIZE_8B));
+
+ /* Disable IRQs */
+ reg_write(spi, APPLE_SPI_IE_FIFO, 0);
+ reg_write(spi, APPLE_SPI_IE_XFER, 0);
+
+ /* Disable delays */
+ reg_write(spi, APPLE_SPI_DELAY_PRE, 0);
+ reg_write(spi, APPLE_SPI_DELAY_POST, 0);
+}
+
+static int apple_spi_prepare_message(struct spi_controller *ctlr, struct spi_message *msg)
+{
+ struct apple_spi *spi = spi_controller_get_devdata(ctlr);
+ struct spi_device *device = msg->spi;
+
+ u32 cfg = ((device->mode & SPI_CPHA ? APPLE_SPI_CFG_CPHA : 0) |
+ (device->mode & SPI_CPOL ? APPLE_SPI_CFG_CPOL : 0) |
+ (device->mode & SPI_LSB_FIRST ? APPLE_SPI_CFG_LSB_FIRST : 0));
+
+ /* Update core config */
+ reg_mask(spi, APPLE_SPI_CFG,
+ APPLE_SPI_CFG_CPHA | APPLE_SPI_CFG_CPOL | APPLE_SPI_CFG_LSB_FIRST, cfg);
+
+ return 0;
+}
+
+static void apple_spi_set_cs(struct spi_device *device, bool is_high)
+{
+ struct apple_spi *spi = spi_controller_get_devdata(device->controller);
+
+ reg_mask(spi, APPLE_SPI_PIN, APPLE_SPI_PIN_CS, is_high ? APPLE_SPI_PIN_CS : 0);
+}
+
+static bool apple_spi_prep_transfer(struct apple_spi *spi, struct spi_transfer *t)
+{
+ u32 cr, fifo_threshold;
+
+ /* Calculate and program the clock rate */
+ cr = DIV_ROUND_UP(clk_get_rate(spi->clk), t->speed_hz);
+ reg_write(spi, APPLE_SPI_CLKDIV, min_t(u32, cr, APPLE_SPI_CLKDIV_MAX));
+
+ /* Update bits per word */
+ reg_mask(spi, APPLE_SPI_SHIFTCFG, APPLE_SPI_SHIFTCFG_BITS,
+ FIELD_PREP(APPLE_SPI_SHIFTCFG_BITS, t->bits_per_word));
+
+ /* We will want to poll if the time we need to wait is
+ * less than the context switching time.
+ * Let's call that threshold 5us. The operation will take:
+ * bits_per_word * fifo_threshold / hz <= 5 * 10^-6
+ * 200000 * bits_per_word * fifo_threshold <= hz
+ */
+ fifo_threshold = APPLE_SPI_FIFO_DEPTH / 2;
+ return (200000 * t->bits_per_word * fifo_threshold) <= t->speed_hz;
+}
+
+static irqreturn_t apple_spi_irq(int irq, void *dev_id)
+{
+ struct apple_spi *spi = dev_id;
+ u32 fifo = reg_read(spi, APPLE_SPI_IF_FIFO) & reg_read(spi, APPLE_SPI_IE_FIFO);
+ u32 xfer = reg_read(spi, APPLE_SPI_IF_XFER) & reg_read(spi, APPLE_SPI_IE_XFER);
+
+ if (fifo || xfer) {
+ /* Disable interrupts until next transfer */
+ reg_write(spi, APPLE_SPI_IE_XFER, 0);
+ reg_write(spi, APPLE_SPI_IE_FIFO, 0);
+ complete(&spi->done);
+ return IRQ_HANDLED;
+ }
+
+ return IRQ_NONE;
+}
+
+static int apple_spi_wait(struct apple_spi *spi, u32 fifo_bit, u32 xfer_bit, int poll)
+{
+ int ret = 0;
+
+ if (poll) {
+ u32 fifo, xfer;
+ unsigned long timeout = jiffies + APPLE_SPI_TIMEOUT_MS * HZ / 1000;
+
+ do {
+ fifo = reg_read(spi, APPLE_SPI_IF_FIFO);
+ xfer = reg_read(spi, APPLE_SPI_IF_XFER);
+ if (time_after(jiffies, timeout)) {
+ ret = -ETIMEDOUT;
+ break;
+ }
+ } while (!((fifo & fifo_bit) || (xfer & xfer_bit)));
+ } else {
+ reinit_completion(&spi->done);
+ reg_write(spi, APPLE_SPI_IE_XFER, xfer_bit);
+ reg_write(spi, APPLE_SPI_IE_FIFO, fifo_bit);
+
+ if (!wait_for_completion_timeout(&spi->done,
+ msecs_to_jiffies(APPLE_SPI_TIMEOUT_MS)))
+ ret = -ETIMEDOUT;
+
+ reg_write(spi, APPLE_SPI_IE_XFER, 0);
+ reg_write(spi, APPLE_SPI_IE_FIFO, 0);
+ }
+
+ return ret;
+}
+
+static void apple_spi_tx(struct apple_spi *spi, const void **tx_ptr, u32 *left,
+ unsigned int bytes_per_word)
+{
+ u32 inuse, words, wrote;
+
+ if (!*tx_ptr)
+ return;
+
+ inuse = FIELD_GET(APPLE_SPI_FIFOSTAT_LEVEL_TX, reg_read(spi, APPLE_SPI_FIFOSTAT));
+ words = wrote = min_t(u32, *left, APPLE_SPI_FIFO_DEPTH - inuse);
+
+ if (!words)
+ return;
+
+ *left -= words;
+
+ switch (bytes_per_word) {
+ case 1: {
+ const u8 *p = *tx_ptr;
+
+ while (words--)
+ reg_write(spi, APPLE_SPI_TXDATA, *p++);
+ break;
+ }
+ case 2: {
+ const u16 *p = *tx_ptr;
+
+ while (words--)
+ reg_write(spi, APPLE_SPI_TXDATA, *p++);
+ break;
+ }
+ case 4: {
+ const u32 *p = *tx_ptr;
+
+ while (words--)
+ reg_write(spi, APPLE_SPI_TXDATA, *p++);
+ break;
+ }
+ default:
+ WARN_ON(1);
+ }
+
+ *tx_ptr = ((u8 *)*tx_ptr) + bytes_per_word * wrote;
+}
+
+static void apple_spi_rx(struct apple_spi *spi, void **rx_ptr, u32 *left,
+ unsigned int bytes_per_word)
+{
+ u32 words, read;
+
+ if (!*rx_ptr)
+ return;
+
+ words = read = FIELD_GET(APPLE_SPI_FIFOSTAT_LEVEL_RX, reg_read(spi, APPLE_SPI_FIFOSTAT));
+ WARN_ON(words > *left);
+
+ if (!words)
+ return;
+
+ *left -= min_t(u32, *left, words);
+
+ switch (bytes_per_word) {
+ case 1: {
+ u8 *p = *rx_ptr;
+
+ while (words--)
+ *p++ = reg_read(spi, APPLE_SPI_RXDATA);
+ break;
+ }
+ case 2: {
+ u16 *p = *rx_ptr;
+
+ while (words--)
+ *p++ = reg_read(spi, APPLE_SPI_RXDATA);
+ break;
+ }
+ case 4: {
+ u32 *p = *rx_ptr;
+
+ while (words--)
+ *p++ = reg_read(spi, APPLE_SPI_RXDATA);
+ break;
+ }
+ default:
+ WARN_ON(1);
+ }
+
+ *rx_ptr = ((u8 *)*rx_ptr) + bytes_per_word * read;
+}
+
+static int apple_spi_transfer_one(struct spi_controller *ctlr, struct spi_device *device,
+ struct spi_transfer *t)
+{
+ struct apple_spi *spi = spi_controller_get_devdata(ctlr);
+ bool poll = apple_spi_prep_transfer(spi, t);
+ const void *tx_ptr = t->tx_buf;
+ void *rx_ptr = t->rx_buf;
+ unsigned int bytes_per_word;
+ u32 words, remaining_tx, remaining_rx;
+ u32 xfer_flags = 0;
+ u32 fifo_flags;
+ int retries = 100;
+ int ret = 0;
+
+ if (t->bits_per_word > 16)
+ bytes_per_word = 4;
+ else if (t->bits_per_word > 8)
+ bytes_per_word = 2;
+ else
+ bytes_per_word = 1;
+
+ words = t->len / bytes_per_word;
+ remaining_tx = tx_ptr ? words : 0;
+ remaining_rx = rx_ptr ? words : 0;
+
+ /* Reset FIFOs */
+ reg_write(spi, APPLE_SPI_CTRL, APPLE_SPI_CTRL_RX_RESET | APPLE_SPI_CTRL_TX_RESET);
+
+ /* Clear IRQ flags */
+ reg_write(spi, APPLE_SPI_IF_XFER, ~0);
+ reg_write(spi, APPLE_SPI_IF_FIFO, ~0);
+
+ /* Determine transfer completion flags we wait for */
+ if (tx_ptr)
+ xfer_flags |= APPLE_SPI_XFER_TXCOMPLETE;
+ if (rx_ptr)
+ xfer_flags |= APPLE_SPI_XFER_RXCOMPLETE;
+
+ /* Set transfer length */
+ reg_write(spi, APPLE_SPI_TXCNT, remaining_tx);
+ reg_write(spi, APPLE_SPI_RXCNT, remaining_rx);
+
+ /* Prime transmit FIFO */
+ apple_spi_tx(spi, &tx_ptr, &remaining_tx, bytes_per_word);
+
+ /* Start transfer */
+ reg_write(spi, APPLE_SPI_CTRL, APPLE_SPI_CTRL_RUN);
+
+ /* TX again since a few words get popped off immediately */
+ apple_spi_tx(spi, &tx_ptr, &remaining_tx, bytes_per_word);
+
+ while (xfer_flags) {
+ fifo_flags = 0;
+
+ if (remaining_tx)
+ fifo_flags |= APPLE_SPI_FIFO_TXTHRESH;
+ if (remaining_rx)
+ fifo_flags |= APPLE_SPI_FIFO_RXTHRESH;
+
+ /* Wait for anything to happen */
+ ret = apple_spi_wait(spi, fifo_flags, xfer_flags, poll);
+ if (ret) {
+ dev_err(&ctlr->dev, "transfer timed out (remaining %d tx, %d rx)\n",
+ remaining_tx, remaining_rx);
+ goto err;
+ }
+
+ /* Stop waiting on transfer halves once they complete */
+ xfer_flags &= ~reg_read(spi, APPLE_SPI_IF_XFER);
+
+ /* Transmit and receive everything we can */
+ apple_spi_tx(spi, &tx_ptr, &remaining_tx, bytes_per_word);
+ apple_spi_rx(spi, &rx_ptr, &remaining_rx, bytes_per_word);
+ }
+
+ /*
+ * Sometimes the transfer completes before the last word is in the RX FIFO.
+ * Normally one retry is all it takes to get the last word out.
+ */
+ while (remaining_rx && retries--)
+ apple_spi_rx(spi, &rx_ptr, &remaining_rx, bytes_per_word);
+
+ if (remaining_tx)
+ dev_err(&ctlr->dev, "transfer completed with %d words left to transmit\n",
+ remaining_tx);
+ if (remaining_rx)
+ dev_err(&ctlr->dev, "transfer completed with %d words left to receive\n",
+ remaining_rx);
+
+err:
+ fifo_flags = reg_read(spi, APPLE_SPI_IF_FIFO);
+ WARN_ON(fifo_flags & APPLE_SPI_FIFO_TXOVERFLOW);
+ WARN_ON(fifo_flags & APPLE_SPI_FIFO_RXUNDERRUN);
+
+ /* Stop transfer */
+ reg_write(spi, APPLE_SPI_CTRL, 0);
+
+ return ret;
+}
+
+static void apple_spi_clk_disable_unprepare(void *data)
+{
+ clk_disable_unprepare(data);
+}
+
+static int apple_spi_probe(struct platform_device *pdev)
+{
+ struct apple_spi *spi;
+ int ret, irq;
+ struct spi_controller *ctlr;
+
+ ctlr = devm_spi_alloc_master(&pdev->dev, sizeof(struct apple_spi));
+ if (!ctlr)
+ return dev_err_probe(&pdev->dev, -ENOMEM, "out of memory\n");
+
+ spi = spi_controller_get_devdata(ctlr);
+ init_completion(&spi->done);
+ platform_set_drvdata(pdev, ctlr);
+
+ spi->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(spi->regs))
+ return PTR_ERR(spi->regs);
+
+ spi->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(spi->clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(spi->clk), "Unable to find bus clock\n");
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ return irq;
+ }
+
+ ret = devm_request_irq(&pdev->dev, irq, apple_spi_irq, 0,
+ dev_name(&pdev->dev), spi);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "Unable to bind to interrupt\n");
+
+ ret = clk_prepare_enable(spi->clk);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "Unable to enable bus clock\n");
+
+ ret = devm_add_action_or_reset(&pdev->dev, apple_spi_clk_disable_unprepare, spi->clk);
+ if (ret)
+ return ret;
+
+ ctlr->dev.of_node = pdev->dev.of_node;
+ ctlr->bus_num = pdev->id;
+ ctlr->num_chipselect = 1;
+ ctlr->mode_bits = SPI_CPHA | SPI_CPOL | SPI_LSB_FIRST;
+ ctlr->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 32);
+ ctlr->flags = 0;
+ ctlr->prepare_message = apple_spi_prepare_message;
+ ctlr->set_cs = apple_spi_set_cs;
+ ctlr->transfer_one = apple_spi_transfer_one;
+ ctlr->auto_runtime_pm = true;
+
+ pm_runtime_set_active(&pdev->dev);
+ devm_pm_runtime_enable(&pdev->dev);
+
+ apple_spi_init(spi);
+
+ ret = devm_spi_register_controller(&pdev->dev, ctlr);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret, "devm_spi_register_controller failed\n");
+
+ return 0;
+}
+
+static const struct of_device_id apple_spi_of_match[] = {
+ { .compatible = "apple,spi", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, apple_spi_of_match);
+
+static struct platform_driver apple_spi_driver = {
+ .probe = apple_spi_probe,
+ .driver = {
+ .name = "apple-spi",
+ .owner = THIS_MODULE,
+ .of_match_table = apple_spi_of_match,
+ },
+};
+module_platform_driver(apple_spi_driver);
+
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
+MODULE_DESCRIPTION("Apple SoC SPI driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/spmi/Kconfig b/drivers/spmi/Kconfig
index 7378020..96c73c5b 100644
--- a/drivers/spmi/Kconfig
+++ b/drivers/spmi/Kconfig
@@ -45,4 +45,12 @@
This is required for communicating with Mediatek PMICs and
other devices that have the SPMI interface.
+config SPMI_APPLE
+ tristate "Apple SoC SPMI Controller platform driver"
+ depends on ARCH_APPLE || COMPILE_TEST
+ help
+ This enables basic support for the SPMI controller present on
+ many Apple SoCs, including the t8103 (M1) and t600x
+ (M1 Pro/Max).
+
endif
diff --git a/drivers/spmi/Makefile b/drivers/spmi/Makefile
index 9d97442..989b84b 100644
--- a/drivers/spmi/Makefile
+++ b/drivers/spmi/Makefile
@@ -7,3 +7,4 @@
obj-$(CONFIG_SPMI_HISI3670) += hisi-spmi-controller.o
obj-$(CONFIG_SPMI_MSM_PMIC_ARB) += spmi-pmic-arb.o
obj-$(CONFIG_SPMI_MTK_PMIF) += spmi-mtk-pmif.o
+obj-$(CONFIG_SPMI_APPLE) += spmi-apple-controller.o
diff --git a/drivers/spmi/spmi-apple-controller.c b/drivers/spmi/spmi-apple-controller.c
new file mode 100644
index 0000000..c14c487
--- /dev/null
+++ b/drivers/spmi/spmi-apple-controller.c
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Apple SoC SPMI device driver
+ *
+ * Copyright The Asahi Linux Contributors
+ *
+ * Inspired by:
+ * OpenBSD support Copyright (c) 2021 Mark Kettenis <kettenis@openbsd.org>
+ * Correllium support Copyright (C) 2021 Corellium LLC
+ * hisi-spmi-controller.c
+ * spmi-pmic-ard.c Copyright (c) 2021, The Linux Foundation.
+ */
+
+#include <linux/bits.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/spmi.h>
+
+/* SPMI Controller Registers */
+#define SPMI_STATUS_REG 0
+#define SPMI_CMD_REG 0x4
+#define SPMI_RSP_REG 0x8
+
+#define SPMI_RX_FIFO_EMPTY BIT(24)
+#define SPMI_TX_FIFO_EMPTY BIT(8)
+
+/* Apple SPMI controler */
+struct apple_spmi {
+ void __iomem *regs;
+ struct spmi_controller *ctrl;
+};
+
+static inline u32 read_reg(struct apple_spmi *spmi, int offset)
+{
+ return (readl_relaxed(spmi->regs + offset));
+}
+
+static inline void write_reg(u32 value, struct apple_spmi *spmi, int offset)
+{
+ writel_relaxed(value, spmi->regs + offset);
+}
+
+static int spmi_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 slave_id,
+ u16 slave_addr, u8 *__buf, size_t bc)
+{
+ struct apple_spmi *spmi;
+ u32 spmi_cmd = opc | slave_id << 8 | slave_addr << 16 | (bc - 1) |
+ (1 << 15);
+ u32 rsp;
+ volatile u32 status;
+ size_t len_to_read;
+ u8 i;
+
+ spmi = spmi_controller_get_drvdata(ctrl);
+
+ write_reg(spmi_cmd, spmi, SPMI_CMD_REG);
+
+ /* Wait for Rx FIFO to have something */
+ /* Quite ugly msleep, need to find a better way to do it */
+ i = 0;
+ do {
+ status = read_reg(spmi, SPMI_STATUS_REG);
+ msleep(10);
+ i += 1;
+ } while ((status & SPMI_RX_FIFO_EMPTY) && i < 5);
+
+ if (i >= 5) {
+ dev_err(&ctrl->dev,
+ "spmi_read_cmd:took to long to get the status");
+ return -1;
+ }
+
+ /* Read SPMI reply status */
+ rsp = read_reg(spmi, SPMI_RSP_REG);
+
+ len_to_read = 0;
+ /* Read SPMI data reply */
+ while (!(status & SPMI_RX_FIFO_EMPTY) && (len_to_read < bc)) {
+ rsp = read_reg(spmi, SPMI_RSP_REG);
+ i = 0;
+ while ((len_to_read < bc) && (i < 4)) {
+ __buf[len_to_read++] = ((0xff << (8 * i)) & rsp) >>
+ (8 * i);
+ i += 1;
+ }
+ }
+
+ return 0;
+}
+
+static int spmi_write_cmd(struct spmi_controller *ctrl, u8 opc, u8 slave_id,
+ u16 slave_addr, const u8 *__buf, size_t bc)
+{
+ struct apple_spmi *spmi;
+ u32 spmi_cmd = opc | slave_id << 8 | slave_addr << 16 | (bc - 1) |
+ (1 << 15);
+ volatile u32 rsp;
+ volatile u32 status;
+ size_t i = 0, j;
+
+ spmi = spmi_controller_get_drvdata(ctrl);
+
+ write_reg(spmi_cmd, spmi, SPMI_CMD_REG);
+
+ while (i < bc) {
+ j = 0;
+ spmi_cmd = 0;
+ while ((j < 4) & (i < bc)) {
+ spmi_cmd |= __buf[i++] << (j++ * 8);
+ }
+ write_reg(spmi_cmd, spmi, SPMI_CMD_REG);
+ }
+
+ /* Wait for Rx FIFO to have something */
+ /* Quite ugly msleep, need to find a better way to do it */
+ i = 0;
+ do {
+ status = read_reg(spmi, SPMI_STATUS_REG);
+ msleep(10);
+ i += 1;
+ } while ((status & SPMI_RX_FIFO_EMPTY) && i < 5);
+
+ if (i >= 5) {
+ dev_err(&ctrl->dev,
+ "spmi_write_cmd:took to long to get the status");
+ return -1;
+ }
+
+ rsp = read_reg(spmi, SPMI_RSP_REG);
+ (void)rsp; // TODO: check stuff here
+
+ return 0;
+}
+
+static int spmi_controller_probe(struct platform_device *pdev)
+{
+ struct apple_spmi *spmi;
+ struct spmi_controller *ctrl;
+ int ret;
+
+ ctrl = spmi_controller_alloc(&pdev->dev, sizeof(struct apple_spmi));
+ if (IS_ERR(ctrl)) {
+ dev_err_probe(&pdev->dev, PTR_ERR(ctrl),
+ "Can't allocate spmi_controller data\n");
+ return -ENOMEM;
+ }
+
+ spmi = spmi_controller_get_drvdata(ctrl);
+ spmi->ctrl = ctrl;
+ platform_set_drvdata(pdev, ctrl);
+
+ spmi->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(spmi->regs)) {
+ dev_err_probe(&pdev->dev, PTR_ERR(spmi->regs),
+ "Can't get ioremap regs.\n");
+ return PTR_ERR(spmi->regs);
+ }
+
+ ctrl->dev.of_node = of_node_get(pdev->dev.of_node);
+
+ /* Callbacks */
+ ctrl->read_cmd = spmi_read_cmd;
+ ctrl->write_cmd = spmi_write_cmd;
+
+ ret = spmi_controller_add(ctrl);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "spmi_controller_add failed with error %d!\n", ret);
+ goto err_put_controller;
+ }
+
+ /* Let's look for other nodes in device tree like the rtc */
+ ret = devm_of_platform_populate(&pdev->dev);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "spmi_controller_probe: devm_of_platform_populate failed with error %d!\n",
+ ret);
+ goto err_devm_of_platform_populate;
+ }
+
+ return 0;
+
+err_put_controller:
+ spmi_controller_put(ctrl);
+err_devm_of_platform_populate:
+ return ret;
+}
+
+static int spmi_del_controller(struct platform_device *pdev)
+{
+ struct spmi_controller *ctrl = platform_get_drvdata(pdev);
+
+ spmi_controller_remove(ctrl);
+ spmi_controller_put(ctrl);
+ return 0;
+}
+
+static const struct of_device_id spmi_controller_match_table[] = {
+ {
+ .compatible = "apple,spmi",
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(of, spmi_controller_match_table);
+
+static struct platform_driver spmi_controller_driver = {
+ .probe = spmi_controller_probe,
+ .remove = spmi_del_controller,
+ .driver = {
+ .name = "apple-spmi",
+ .owner = THIS_MODULE,
+ .of_match_table = spmi_controller_match_table,
+ },
+};
+module_platform_driver(spmi_controller_driver);
+
+MODULE_AUTHOR("Jean-Francois Bortolotti <jeff@borto.fr>");
+MODULE_DESCRIPTION("Apple SoC SPMI driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/tty/serial/samsung_tty.c b/drivers/tty/serial/samsung_tty.c
index 3bd5528..89929cf 100644
--- a/drivers/tty/serial/samsung_tty.c
+++ b/drivers/tty/serial/samsung_tty.c
@@ -41,6 +41,7 @@
#include <linux/clk.h>
#include <linux/cpufreq.h>
#include <linux/of.h>
+#include <linux/pm_runtime.h>
#include <asm/irq.h>
/* UART name and device definitions */
@@ -1294,30 +1295,49 @@ static int apple_s5l_serial_startup(struct uart_port *port)
/* power power management control */
+static int __maybe_unused s3c24xx_serial_runtime_suspend(struct device *dev)
+{
+ struct uart_port *port = dev_get_drvdata(dev);
+ struct s3c24xx_uart_port *ourport = to_ourport(port);
+ int timeout = 10000;
+
+ while (--timeout && !s3c24xx_serial_txempty_nofifo(port))
+ udelay(100);
+
+ if (!IS_ERR(ourport->baudclk))
+ clk_disable_unprepare(ourport->baudclk);
+
+ clk_disable_unprepare(ourport->clk);
+ return 0;
+};
+
+static int __maybe_unused s3c24xx_serial_runtime_resume(struct device *dev)
+{
+ struct uart_port *port = dev_get_drvdata(dev);
+ struct s3c24xx_uart_port *ourport = to_ourport(port);
+
+ clk_prepare_enable(ourport->clk);
+
+ if (!IS_ERR(ourport->baudclk))
+ clk_prepare_enable(ourport->baudclk);
+ return 0;
+};
+
static void s3c24xx_serial_pm(struct uart_port *port, unsigned int level,
unsigned int old)
{
struct s3c24xx_uart_port *ourport = to_ourport(port);
- int timeout = 10000;
ourport->pm_level = level;
switch (level) {
- case 3:
- while (--timeout && !s3c24xx_serial_txempty_nofifo(port))
- udelay(100);
-
- if (!IS_ERR(ourport->baudclk))
- clk_disable_unprepare(ourport->baudclk);
-
- clk_disable_unprepare(ourport->clk);
+ case UART_PM_STATE_OFF:
+ pm_runtime_mark_last_busy(port->dev);
+ pm_runtime_put_sync(port->dev);
break;
- case 0:
- clk_prepare_enable(ourport->clk);
-
- if (!IS_ERR(ourport->baudclk))
- clk_prepare_enable(ourport->baudclk);
+ case UART_PM_STATE_ON:
+ pm_runtime_get_sync(port->dev);
break;
default:
dev_err(port->dev, "s3c24xx_serial: unknown pm %d\n", level);
@@ -2036,18 +2056,15 @@ static int s3c24xx_serial_probe(struct platform_device *pdev)
}
}
+ pm_runtime_get_noresume(&pdev->dev);
+ pm_runtime_set_active(&pdev->dev);
+ pm_runtime_enable(&pdev->dev);
+
dev_dbg(&pdev->dev, "%s: adding port\n", __func__);
uart_add_one_port(&s3c24xx_uart_drv, &ourport->port);
platform_set_drvdata(pdev, &ourport->port);
- /*
- * Deactivate the clock enabled in s3c24xx_serial_init_port here,
- * so that a potential re-enablement through the pm-callback overlaps
- * and keeps the clock enabled in this case.
- */
- clk_disable_unprepare(ourport->clk);
- if (!IS_ERR(ourport->baudclk))
- clk_disable_unprepare(ourport->baudclk);
+ pm_runtime_put_sync(&pdev->dev);
probe_index++;
@@ -2057,9 +2074,19 @@ static int s3c24xx_serial_probe(struct platform_device *pdev)
static int s3c24xx_serial_remove(struct platform_device *dev)
{
struct uart_port *port = s3c24xx_dev_to_port(&dev->dev);
+ struct s3c24xx_uart_port *ourport = to_ourport(port);
if (port) {
+ pm_runtime_get_sync(&dev->dev);
uart_remove_one_port(&s3c24xx_uart_drv, port);
+
+ clk_disable_unprepare(ourport->clk);
+ if (!IS_ERR(ourport->baudclk))
+ clk_disable_unprepare(ourport->baudclk);
+
+ pm_runtime_disable(&dev->dev);
+ pm_runtime_set_suspended(&dev->dev);
+ pm_runtime_put_noidle(&dev->dev);
}
uart_unregister_driver(&s3c24xx_uart_drv);
@@ -2068,18 +2095,21 @@ static int s3c24xx_serial_remove(struct platform_device *dev)
}
/* UART power management code */
-#ifdef CONFIG_PM_SLEEP
-static int s3c24xx_serial_suspend(struct device *dev)
+
+static int __maybe_unused s3c24xx_serial_suspend(struct device *dev)
{
struct uart_port *port = s3c24xx_dev_to_port(dev);
+ if (!console_suspend_enabled && uart_console(port))
+ device_set_wakeup_path(dev);
+
if (port)
uart_suspend_port(&s3c24xx_uart_drv, port);
return 0;
}
-static int s3c24xx_serial_resume(struct device *dev)
+static int __maybe_unused s3c24xx_serial_resume(struct device *dev)
{
struct uart_port *port = s3c24xx_dev_to_port(dev);
struct s3c24xx_uart_port *ourport = to_ourport(port);
@@ -2099,7 +2129,7 @@ static int s3c24xx_serial_resume(struct device *dev)
return 0;
}
-static int s3c24xx_serial_resume_noirq(struct device *dev)
+static int __maybe_unused s3c24xx_serial_resume_noirq(struct device *dev)
{
struct uart_port *port = s3c24xx_dev_to_port(dev);
struct s3c24xx_uart_port *ourport = to_ourport(port);
@@ -2172,12 +2202,6 @@ static const struct dev_pm_ops s3c24xx_serial_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(s3c24xx_serial_suspend, s3c24xx_serial_resume)
SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(NULL, s3c24xx_serial_resume_noirq)
};
-#define SERIAL_SAMSUNG_PM_OPS (&s3c24xx_serial_pm_ops)
-
-#else /* !CONFIG_PM_SLEEP */
-
-#define SERIAL_SAMSUNG_PM_OPS NULL
-#endif /* CONFIG_PM_SLEEP */
/* Console code */
@@ -2615,7 +2639,7 @@ static struct platform_driver samsung_serial_driver = {
.id_table = s3c24xx_serial_driver_ids,
.driver = {
.name = "samsung-uart",
- .pm = SERIAL_SAMSUNG_PM_OPS,
+ .pm = &s3c24xx_serial_pm_ops,
.of_match_table = of_match_ptr(s3c24xx_uart_dt_match),
},
};
diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index 0328c86..cc464b5 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -116,6 +116,9 @@ void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode)
dwc->current_dr_role = mode;
}
+static void dwc3_core_exit(struct dwc3 *dwc);
+static int dwc3_core_init_for_resume(struct dwc3 *dwc);
+
static void __dwc3_set_mode(struct work_struct *work)
{
struct dwc3 *dwc = work_to_dwc(work);
@@ -134,7 +137,7 @@ static void __dwc3_set_mode(struct work_struct *work)
if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_OTG)
dwc3_otg_update(dwc, 0);
- if (!desired_dr_role)
+ if (!desired_dr_role && !dwc->role_switch_reset_quirk)
goto out;
if (desired_dr_role == dwc->current_dr_role)
@@ -162,13 +165,32 @@ static void __dwc3_set_mode(struct work_struct *work)
break;
}
+ if (dwc->role_switch_reset_quirk) {
+ if (dwc->current_dr_role) {
+ dwc->current_dr_role = 0;
+ dwc3_core_exit(dwc);
+ }
+
+ if (desired_dr_role) {
+ ret = dwc3_core_init_for_resume(dwc);
+ if (ret) {
+ dev_err(dwc->dev,
+ "failed to reinitialize core\n");
+ goto out;
+ }
+ } else {
+ goto out;
+ }
+ }
+
/*
* When current_dr_role is not set, there's no role switching.
* Only perform GCTL.CoreSoftReset when there's DRD role switching.
*/
- if (dwc->current_dr_role && ((DWC3_IP_IS(DWC3) ||
+ if (dwc->role_switch_reset_quirk ||
+ (dwc->current_dr_role && ((DWC3_IP_IS(DWC3) ||
DWC3_VER_IS_PRIOR(DWC31, 190A)) &&
- desired_dr_role != DWC3_GCTL_PRTCAP_OTG)) {
+ desired_dr_role != DWC3_GCTL_PRTCAP_OTG))) {
reg = dwc3_readl(dwc->regs, DWC3_GCTL);
reg |= DWC3_GCTL_CORESOFTRESET;
dwc3_writel(dwc->regs, DWC3_GCTL, reg);
@@ -1465,6 +1487,18 @@ static int dwc3_core_init_mode(struct dwc3 *dwc)
ret = dwc3_drd_init(dwc);
if (ret)
return dev_err_probe(dev, ret, "failed to initialize dual-role\n");
+
+ /*
+ * If the role switch reset quirk is required the first role
+ * switch notification will initialize the core such that we
+ * have to shut it down here. Make sure that the __dwc3_set_mode
+ * queued by dwc3_drd_init has completed before since it
+ * may still try to access MMIO.
+ */
+ if (dwc->role_switch_reset_quirk) {
+ flush_work(&dwc->drd_work);
+ dwc3_core_exit(dwc);
+ }
break;
default:
dev_err(dev, "Unsupported mode of operation %d\n", dwc->dr_mode);
@@ -1967,6 +2001,22 @@ static int dwc3_probe(struct platform_device *pdev)
if (ret)
goto err_put_psy;
+ if (dev->of_node) {
+ if (of_device_is_compatible(dev->of_node, "apple,dwc3")) {
+ if (!IS_ENABLED(CONFIG_USB_ROLE_SWITCH) ||
+ !IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)) {
+ dev_err(dev,
+ "Apple DWC3 requires role switch support.\n"
+ );
+ ret = -EINVAL;
+ goto err_put_psy;
+ }
+
+ dwc->dr_mode = USB_DR_MODE_OTG;
+ dwc->role_switch_reset_quirk = true;
+ }
+ }
+
ret = reset_control_deassert(dwc->reset);
if (ret)
goto err_put_psy;
@@ -2090,7 +2140,6 @@ static void dwc3_remove(struct platform_device *pdev)
power_supply_put(dwc->usb_psy);
}
-#ifdef CONFIG_PM
static int dwc3_core_init_for_resume(struct dwc3 *dwc)
{
int ret;
@@ -2117,6 +2166,7 @@ static int dwc3_core_init_for_resume(struct dwc3 *dwc)
return ret;
}
+#ifdef CONFIG_PM
static int dwc3_suspend_common(struct dwc3 *dwc, pm_message_t msg)
{
unsigned long flags;
diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index efe6caf..82935b9 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -1129,6 +1129,7 @@ struct dwc3_scratchpad_array {
* @dis_split_quirk: set to disable split boundary.
* @wakeup_configured: set if the device is configured for remote wakeup.
* @suspended: set to track suspend event due to U3/L2.
+ * @role_switch_reset_quirk: set to force reinitialization after any role switch
* @imod_interval: set the interrupt moderation interval in 250ns
* increments or 0 to disable.
* @max_cfg_eps: current max number of IN eps used across all USB configs.
@@ -1353,6 +1354,8 @@ struct dwc3 {
unsigned wakeup_configured:1;
unsigned suspended:1;
+ unsigned role_switch_reset_quirk:1;
+
u16 imod_interval;
int max_cfg_eps;
diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c
index 039bf24..e30f048 100644
--- a/drivers/usb/dwc3/drd.c
+++ b/drivers/usb/dwc3/drd.c
@@ -461,6 +461,9 @@ static int dwc3_usb_role_switch_set(struct usb_role_switch *sw,
break;
}
+ if (dwc->role_switch_reset_quirk && role == USB_ROLE_NONE)
+ mode = 0;
+
dwc3_set_mode(dwc, mode);
return 0;
}
@@ -489,6 +492,10 @@ static enum usb_role dwc3_usb_role_switch_get(struct usb_role_switch *sw)
role = USB_ROLE_DEVICE;
break;
}
+
+ if (dwc->role_switch_reset_quirk && !dwc->current_dr_role)
+ role = USB_ROLE_NONE;
+
spin_unlock_irqrestore(&dwc->lock, flags);
return role;
}
@@ -499,7 +506,9 @@ static int dwc3_setup_role_switch(struct dwc3 *dwc)
u32 mode;
dwc->role_switch_default_mode = usb_get_role_switch_default_mode(dwc->dev);
- if (dwc->role_switch_default_mode == USB_DR_MODE_HOST) {
+ if (dwc->role_switch_reset_quirk) {
+ mode = 0;
+ } else if (dwc->role_switch_default_mode == USB_DR_MODE_HOST) {
mode = DWC3_GCTL_PRTCAP_HOST;
} else {
dwc->role_switch_default_mode = USB_DR_MODE_PERIPHERAL;
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index 4448d0a..fdcbc71 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -40,17 +40,26 @@
config USB_XHCI_PCI
tristate
depends on USB_PCI
- depends on USB_XHCI_PCI_RENESAS || !USB_XHCI_PCI_RENESAS
default y
config USB_XHCI_PCI_RENESAS
- tristate "Support for additional Renesas xHCI controller with firmware"
+ bool "Support for Renesas xHCI controllers with firmware"
+ depends on USB_XHCI_PCI
help
Say 'Y' to enable the support for the Renesas xHCI controller with
firmware. Make sure you have the firmware for the device and
installed on your system for this device to work.
If unsure, say 'N'.
+config USB_XHCI_PCI_ASMEDIA
+ bool "Support for ASMedia xHCI controller with firmware"
+ default ARCH_APPLE
+ depends on USB_XHCI_PCI
+ help
+ Say 'Y' to enable support for ASMedia xHCI controllers with
+ host-supplied firmware. These are usually present on Apple devices.
+ If unsure, say 'N'.
+
config USB_XHCI_PLATFORM
tristate "Generic xHCI driver for a platform device"
help
diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile
index be4e524..b64cb33 100644
--- a/drivers/usb/host/Makefile
+++ b/drivers/usb/host/Makefile
@@ -68,7 +68,9 @@
obj-$(CONFIG_USB_FHCI_HCD) += fhci.o
obj-$(CONFIG_USB_XHCI_HCD) += xhci-hcd.o
obj-$(CONFIG_USB_XHCI_PCI) += xhci-pci.o
-obj-$(CONFIG_USB_XHCI_PCI_RENESAS) += xhci-pci-renesas.o
+xhci-pci-y += xhci-pci-core.o
+xhci-pci-$(CONFIG_USB_XHCI_PCI_RENESAS) += xhci-pci-renesas.o
+xhci-pci-$(CONFIG_USB_XHCI_PCI_ASMEDIA) += xhci-pci-asmedia.o
obj-$(CONFIG_USB_XHCI_PLATFORM) += xhci-plat-hcd.o
obj-$(CONFIG_USB_XHCI_HISTB) += xhci-histb.o
obj-$(CONFIG_USB_XHCI_RCAR) += xhci-rcar-hcd.o
diff --git a/drivers/usb/host/xhci-pci-asmedia.c b/drivers/usb/host/xhci-pci-asmedia.c
new file mode 100644
index 0000000..80f1b3e
--- /dev/null
+++ b/drivers/usb/host/xhci-pci-asmedia.c
@@ -0,0 +1,394 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+/*
+ * ASMedia xHCI firmware loader
+ * Copyright (C) The Asahi Linux Contributors
+ */
+
+#include <linux/acpi.h>
+#include <linux/firmware.h>
+#include <linux/pci.h>
+#include <linux/iopoll.h>
+#include <linux/slab.h>
+#include <asm/unaligned.h>
+
+#include "xhci.h"
+#include "xhci-trace.h"
+#include "xhci-pci.h"
+
+/* Configuration space registers */
+#define ASMT_CFG_CONTROL 0xe0
+#define ASMT_CFG_CONTROL_WRITE BIT(1)
+#define ASMT_CFG_CONTROL_READ BIT(0)
+
+#define ASMT_CFG_SRAM_ADDR 0xe2
+
+#define ASMT_CFG_SRAM_ACCESS 0xef
+#define ASMT_CFG_SRAM_ACCESS_READ BIT(6)
+#define ASMT_CFG_SRAM_ACCESS_ENABLE BIT(7)
+
+#define ASMT_CFG_DATA_READ0 0xf0
+#define ASMT_CFG_DATA_READ1 0xf4
+
+#define ASMT_CFG_DATA_WRITE0 0xf8
+#define ASMT_CFG_DATA_WRITE1 0xfc
+
+#define ASMT_CMD_GET_FWVER 0x8000060840
+#define ASMT_FWVER_ROM 0x010250090816
+
+/* BAR0 registers */
+#define ASMT_REG_ADDR 0x3000
+
+#define ASMT_REG_WDATA 0x3004
+#define ASMT_REG_RDATA 0x3008
+
+#define ASMT_REG_STATUS 0x3009
+#define ASMT_REG_STATUS_BUSY BIT(7)
+
+#define ASMT_REG_CODE_WDATA 0x3010
+#define ASMT_REG_CODE_RDATA 0x3018
+
+#define ASMT_MMIO_CPU_MISC 0x500e
+#define ASMT_MMIO_CPU_MISC_CODE_RAM_WR BIT(0)
+
+#define ASMT_MMIO_CPU_MODE_NEXT 0x5040
+#define ASMT_MMIO_CPU_MODE_CUR 0x5041
+
+#define ASMT_MMIO_CPU_MODE_RAM BIT(0)
+#define ASMT_MMIO_CPU_MODE_HALFSPEED BIT(1)
+
+#define ASMT_MMIO_CPU_EXEC_CTRL 0x5042
+#define ASMT_MMIO_CPU_EXEC_CTRL_RESET BIT(0)
+#define ASMT_MMIO_CPU_EXEC_CTRL_HALT BIT(1)
+
+#define TIMEOUT_USEC 10000
+#define RESET_TIMEOUT_USEC 500000
+
+static int asmedia_mbox_tx(struct pci_dev *pdev, u64 data)
+{
+ u8 op;
+ int i;
+
+ for (i = 0; i < TIMEOUT_USEC; i++) {
+ pci_read_config_byte(pdev, ASMT_CFG_CONTROL, &op);
+ if (!(op & ASMT_CFG_CONTROL_WRITE))
+ break;
+ udelay(1);
+ }
+
+ if (op & ASMT_CFG_CONTROL_WRITE) {
+ dev_err(&pdev->dev,
+ "Timed out on mailbox tx: 0x%llx\n",
+ data);
+ return -ETIMEDOUT;
+ }
+
+ pci_write_config_dword(pdev, ASMT_CFG_DATA_WRITE0, data);
+ pci_write_config_dword(pdev, ASMT_CFG_DATA_WRITE1, data >> 32);
+ pci_write_config_byte(pdev, ASMT_CFG_CONTROL,
+ ASMT_CFG_CONTROL_WRITE);
+
+ return 0;
+}
+
+static int asmedia_mbox_rx(struct pci_dev *pdev, u64 *data)
+{
+ u8 op;
+ u32 low, high;
+ int i;
+
+ for (i = 0; i < TIMEOUT_USEC; i++) {
+ pci_read_config_byte(pdev, ASMT_CFG_CONTROL, &op);
+ if (op & ASMT_CFG_CONTROL_READ)
+ break;
+ udelay(1);
+ }
+
+ if (!(op & ASMT_CFG_CONTROL_READ)) {
+ dev_err(&pdev->dev, "Timed out on mailbox rx\n");
+ return -ETIMEDOUT;
+ }
+
+ pci_read_config_dword(pdev, ASMT_CFG_DATA_READ0, &low);
+ pci_read_config_dword(pdev, ASMT_CFG_DATA_READ1, &high);
+ pci_write_config_byte(pdev, ASMT_CFG_CONTROL,
+ ASMT_CFG_CONTROL_READ);
+
+ *data = ((u64)high << 32) | low;
+ return 0;
+}
+
+static int asmedia_get_fw_version(struct pci_dev *pdev, u64 *version)
+{
+ int err = 0;
+ u64 cmd;
+
+ err = asmedia_mbox_tx(pdev, ASMT_CMD_GET_FWVER);
+ if (err)
+ return err;
+ err = asmedia_mbox_tx(pdev, 0);
+ if (err)
+ return err;
+
+ err = asmedia_mbox_rx(pdev, &cmd);
+ if (err)
+ return err;
+ err = asmedia_mbox_rx(pdev, version);
+ if (err)
+ return err;
+
+ if (cmd != ASMT_CMD_GET_FWVER) {
+ dev_err(&pdev->dev, "Unexpected reply command 0x%llx\n", cmd);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static bool asmedia_check_firmware(struct pci_dev *pdev)
+{
+ u64 fwver;
+ int ret;
+
+ ret = asmedia_get_fw_version(pdev, &fwver);
+ if (ret)
+ return ret;
+
+ dev_info(&pdev->dev, "Firmware version: 0x%llx\n", fwver);
+
+ return fwver != ASMT_FWVER_ROM;
+}
+
+static int asmedia_wait_reset(struct pci_dev *pdev)
+{
+ struct usb_hcd *hcd = dev_get_drvdata(&pdev->dev);
+ struct xhci_cap_regs __iomem *cap = hcd->regs;
+ struct xhci_op_regs __iomem *op;
+ u32 val;
+ int ret;
+
+ op = hcd->regs + HC_LENGTH(readl(&cap->hc_capbase));
+
+ ret = readl_poll_timeout(&op->command,
+ val, !(val & CMD_RESET),
+ 1000, RESET_TIMEOUT_USEC);
+
+ if (!ret)
+ return 0;
+
+ dev_err(hcd->self.controller, "Reset timed out, trying to kick it\n");
+
+ pci_write_config_byte(pdev, ASMT_CFG_SRAM_ACCESS,
+ ASMT_CFG_SRAM_ACCESS_ENABLE);
+
+ pci_write_config_byte(pdev, ASMT_CFG_SRAM_ACCESS, 0);
+
+ ret = readl_poll_timeout(&op->command,
+ val, !(val & CMD_RESET),
+ 1000, RESET_TIMEOUT_USEC);
+
+ if (ret)
+ dev_err(hcd->self.controller, "Reset timed out, giving up\n");
+
+ return ret;
+}
+
+static u8 asmedia_read_reg(struct usb_hcd *hcd, u16 addr) {
+ void __iomem *regs = hcd->regs;
+ u8 status;
+ int ret;
+
+ ret = readb_poll_timeout(regs + ASMT_REG_STATUS,
+ status, !(status & ASMT_REG_STATUS_BUSY),
+ 1000, TIMEOUT_USEC);
+
+ if (ret) {
+ dev_err(hcd->self.controller,
+ "Read reg wait timed out ([%04x])\n", addr);
+ return ~0;
+ }
+
+ writew_relaxed(addr, regs + ASMT_REG_ADDR);
+
+ ret = readb_poll_timeout(regs + ASMT_REG_STATUS,
+ status, !(status & ASMT_REG_STATUS_BUSY),
+ 1000, TIMEOUT_USEC);
+
+ if (ret) {
+ dev_err(hcd->self.controller,
+ "Read reg addr timed out ([%04x])\n", addr);
+ return ~0;
+ }
+
+ return readb_relaxed(regs + ASMT_REG_RDATA);
+}
+
+static void asmedia_write_reg(struct usb_hcd *hcd, u16 addr, u8 data, bool wait) {
+ void __iomem *regs = hcd->regs;
+ u8 status;
+ int ret, i;
+
+ writew_relaxed(addr, regs + ASMT_REG_ADDR);
+
+ ret = readb_poll_timeout(regs + ASMT_REG_STATUS,
+ status, !(status & ASMT_REG_STATUS_BUSY),
+ 1000, TIMEOUT_USEC);
+
+ if (ret)
+ dev_err(hcd->self.controller,
+ "Write reg addr timed out ([%04x] = %02x)\n",
+ addr, data);
+
+ writeb_relaxed(data, regs + ASMT_REG_WDATA);
+
+ ret = readb_poll_timeout(regs + ASMT_REG_STATUS,
+ status, !(status & ASMT_REG_STATUS_BUSY),
+ 1000, TIMEOUT_USEC);
+
+ if (ret)
+ dev_err(hcd->self.controller,
+ "Write reg data timed out ([%04x] = %02x)\n",
+ addr, data);
+
+ if (!wait)
+ return;
+
+ for (i = 0; i < TIMEOUT_USEC; i++) {
+ if (asmedia_read_reg(hcd, addr) == data)
+ break;
+ }
+
+ if (i >= TIMEOUT_USEC) {
+ dev_err(hcd->self.controller,
+ "Verify register timed out ([%04x] = %02x)\n",
+ addr, data);
+ }
+}
+
+static int asmedia_load_fw(struct pci_dev *pdev, const struct firmware *fw)
+{
+ struct usb_hcd *hcd;
+ void __iomem *regs;
+ const u16 *fw_data = (const u16 *)fw->data;
+ u16 raddr;
+ u32 data;
+ size_t index = 0, addr = 0;
+ size_t words = fw->size >> 1;
+ int ret, i;
+
+ hcd = dev_get_drvdata(&pdev->dev);
+ regs = hcd->regs;
+
+ asmedia_write_reg(hcd, ASMT_MMIO_CPU_MODE_NEXT,
+ ASMT_MMIO_CPU_MODE_HALFSPEED, false);
+
+ asmedia_write_reg(hcd, ASMT_MMIO_CPU_EXEC_CTRL,
+ ASMT_MMIO_CPU_EXEC_CTRL_RESET, false);
+
+ ret = asmedia_wait_reset(pdev);
+ if (ret) {
+ dev_err(hcd->self.controller, "Failed pre-upload reset\n");
+ return ret;
+ }
+
+ asmedia_write_reg(hcd, ASMT_MMIO_CPU_EXEC_CTRL,
+ ASMT_MMIO_CPU_EXEC_CTRL_HALT, false);
+
+ asmedia_write_reg(hcd, ASMT_MMIO_CPU_MISC,
+ ASMT_MMIO_CPU_MISC_CODE_RAM_WR, true);
+
+ pci_write_config_byte(pdev, ASMT_CFG_SRAM_ACCESS,
+ ASMT_CFG_SRAM_ACCESS_ENABLE);
+
+ /* The firmware upload is interleaved in 0x4000 word blocks */
+ addr = index = 0;
+ while (index < words) {
+ data = fw_data[index];
+ if ((index | 0x4000) < words)
+ data |= fw_data[index | 0x4000] << 16;
+
+ pci_write_config_word(pdev, ASMT_CFG_SRAM_ADDR,
+ addr);
+
+ writel_relaxed(data, regs + ASMT_REG_CODE_WDATA);
+
+ for (i = 0; i < TIMEOUT_USEC; i++) {
+ pci_read_config_word(pdev, ASMT_CFG_SRAM_ADDR, &raddr);
+ if (raddr != addr)
+ break;
+ udelay(1);
+ }
+
+ if (raddr == addr) {
+ dev_err(hcd->self.controller, "Word write timed out\n");
+ return -ETIMEDOUT;
+ }
+
+ if (++index & 0x4000)
+ index += 0x4000;
+ addr += 2;
+ }
+
+ pci_write_config_byte(pdev, ASMT_CFG_SRAM_ACCESS, 0);
+
+ asmedia_write_reg(hcd, ASMT_MMIO_CPU_MISC, 0, true);
+
+ asmedia_write_reg(hcd, ASMT_MMIO_CPU_MODE_NEXT,
+ ASMT_MMIO_CPU_MODE_RAM |
+ ASMT_MMIO_CPU_MODE_HALFSPEED, false);
+
+ asmedia_write_reg(hcd, ASMT_MMIO_CPU_EXEC_CTRL, 0, false);
+
+ ret = asmedia_wait_reset(pdev);
+ if (ret) {
+ dev_err(hcd->self.controller, "Failed post-upload reset\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+int asmedia_xhci_check_request_fw(struct pci_dev *pdev,
+ const struct pci_device_id *id)
+{
+ struct xhci_driver_data *driver_data =
+ (struct xhci_driver_data *)id->driver_data;
+ const char *fw_name = driver_data->firmware;
+ const struct firmware *fw;
+ int ret;
+
+ /* Check if device has firmware, if so skip everything */
+ ret = asmedia_check_firmware(pdev);
+ if (ret < 0)
+ return ret;
+ else if (ret == 1)
+ return 0;
+
+ pci_dev_get(pdev);
+ ret = request_firmware(&fw, fw_name, &pdev->dev);
+ pci_dev_put(pdev);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not load firmware %s: %d\n",
+ fw_name, ret);
+ return ret;
+ }
+
+ ret = asmedia_load_fw(pdev, fw);
+ if (ret) {
+ dev_err(&pdev->dev, "Firmware upload failed: %d\n", ret);
+ goto err;
+ }
+
+ ret = asmedia_check_firmware(pdev);
+ if (ret < 0) {
+ goto err;
+ } else if (ret != 1) {
+ dev_err(&pdev->dev, "Firmware version is too old after upload\n");
+ ret = -EIO;
+ } else {
+ ret = 0;
+ }
+
+err:
+ release_firmware(fw);
+ return ret;
+}
diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci-core.c
similarity index 97%
rename from drivers/usb/host/xhci-pci.c
rename to drivers/usb/host/xhci-pci-core.c
index 95ed940..54a7c7c 100644
--- a/drivers/usb/host/xhci-pci.c
+++ b/drivers/usb/host/xhci-pci-core.c
@@ -595,6 +595,18 @@ static int xhci_pci_setup(struct usb_hcd *hcd)
struct xhci_hcd *xhci;
struct pci_dev *pdev = to_pci_dev(hcd->self.controller);
int retval;
+ struct xhci_driver_data *driver_data;
+ const struct pci_device_id *id;
+
+ id = pci_match_id(to_pci_driver(pdev->dev.driver)->id_table, pdev);
+ if (id && id->driver_data && usb_hcd_is_primary_hcd(hcd)) {
+ driver_data = (struct xhci_driver_data *)id->driver_data;
+ if (driver_data->quirks & XHCI_ASMEDIA_FW_QUIRK) {
+ retval = asmedia_xhci_check_request_fw(pdev, id);
+ if (retval < 0)
+ return retval;
+ }
+ }
xhci = hcd_to_xhci(hcd);
if (!xhci->sbrn)
@@ -950,6 +962,11 @@ static const struct xhci_driver_data reneses_data = {
.firmware = "renesas_usb_fw.mem",
};
+static const struct xhci_driver_data asmedia_data = {
+ .quirks = XHCI_ASMEDIA_FW_QUIRK,
+ .firmware = "asmedia/asm2214a-apple.bin",
+};
+
/* PCI driver selection metadata; PCI hotplugging uses this */
static const struct pci_device_id pci_ids[] = {
{ PCI_DEVICE(0x1912, 0x0014),
@@ -958,6 +975,9 @@ static const struct pci_device_id pci_ids[] = {
{ PCI_DEVICE(0x1912, 0x0015),
.driver_data = (unsigned long)&reneses_data,
},
+ { PCI_DEVICE(0x1b21, 0x2142),
+ .driver_data = (unsigned long)&asmedia_data,
+ },
/* handle any USB 3.0 xHCI controller */
{ PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_XHCI, ~0),
},
@@ -973,6 +993,10 @@ MODULE_DEVICE_TABLE(pci, pci_ids);
MODULE_FIRMWARE("renesas_usb_fw.mem");
#endif
+#if IS_ENABLED(CONFIG_USB_XHCI_PCI_ASMEDIA)
+MODULE_FIRMWARE("asmedia/asm2214a-apple.bin");
+#endif
+
/* pci driver glue; this is a "new style" PCI driver module */
static struct pci_driver xhci_pci_driver = {
.name = hcd_name,
diff --git a/drivers/usb/host/xhci-pci-renesas.c b/drivers/usb/host/xhci-pci-renesas.c
index 93f8b35..dddd35e 100644
--- a/drivers/usb/host/xhci-pci-renesas.c
+++ b/drivers/usb/host/xhci-pci-renesas.c
@@ -3,7 +3,6 @@
#include <linux/acpi.h>
#include <linux/firmware.h>
-#include <linux/module.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <asm/unaligned.h>
@@ -625,6 +624,5 @@ int renesas_xhci_check_request_fw(struct pci_dev *pdev,
release_firmware(fw);
return err;
}
-EXPORT_SYMBOL_GPL(renesas_xhci_check_request_fw);
-MODULE_LICENSE("GPL v2");
+MODULE_FIRMWARE("renesas_usb_fw.mem");
diff --git a/drivers/usb/host/xhci-pci.h b/drivers/usb/host/xhci-pci.h
index cb9a8f3..279c95a 100644
--- a/drivers/usb/host/xhci-pci.h
+++ b/drivers/usb/host/xhci-pci.h
@@ -9,7 +9,20 @@ int renesas_xhci_check_request_fw(struct pci_dev *dev,
const struct pci_device_id *id);
#else
-static int renesas_xhci_check_request_fw(struct pci_dev *dev,
+static inline int renesas_xhci_check_request_fw(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return 0;
+}
+
+#endif
+
+#if IS_ENABLED(CONFIG_USB_XHCI_PCI_ASMEDIA)
+int asmedia_xhci_check_request_fw(struct pci_dev *dev,
+ const struct pci_device_id *id);
+
+#else
+static inline int asmedia_xhci_check_request_fw(struct pci_dev *dev,
const struct pci_device_id *id)
{
return 0;
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index 3ea5c09..7143049 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -1876,6 +1876,7 @@ struct xhci_hcd {
#define XHCI_RESET_TO_DEFAULT BIT_ULL(44)
#define XHCI_ZHAOXIN_TRB_FETCH BIT_ULL(45)
#define XHCI_ZHAOXIN_HOST BIT_ULL(46)
+#define XHCI_ASMEDIA_FW_QUIRK BIT_ULL(47)
unsigned int num_active_eps;
unsigned int limit_active_eps;
diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c
index 0e867f5..c620b05 100644
--- a/drivers/usb/typec/tipd/core.c
+++ b/drivers/usb/typec/tipd/core.c
@@ -168,11 +168,15 @@ tps6598x_block_read(struct tps6598x *tps, u8 reg, void *val, size_t len)
return regmap_raw_read(tps->regmap, reg, val, len);
ret = regmap_raw_read(tps->regmap, reg, data, len + 1);
- if (ret)
+ if (ret) {
+ dev_err(tps->dev, "regmap_raw_read returned %d\n", ret);
return ret;
+ }
- if (data[0] < len)
+ if (data[0] < len) {
+ dev_err(tps->dev, "expected %zu bytes, got %d\n", len, data[0]);
return -EIO;
+ }
memcpy(val, &data[1], len);
return 0;
@@ -467,7 +471,7 @@ static bool tps6598x_read_status(struct tps6598x *tps, u32 *status)
ret = tps6598x_read32(tps, TPS_REG_STATUS, status);
if (ret) {
- dev_err(tps->dev, "%s: failed to read status\n", __func__);
+ dev_err(tps->dev, "%s: failed to read status: %d\n", __func__, ret);
return false;
}
@@ -542,24 +546,23 @@ static irqreturn_t cd321x_interrupt(int irq, void *data)
if (!event)
goto err_unlock;
+ tps6598x_write64(tps, TPS_REG_INT_CLEAR1, event);
+
if (!tps6598x_read_status(tps, &status))
- goto err_clear_ints;
+ goto err_unlock;
if (event & APPLE_CD_REG_INT_POWER_STATUS_UPDATE)
if (!tps6598x_read_power_status(tps))
- goto err_clear_ints;
+ goto err_unlock;
if (event & APPLE_CD_REG_INT_DATA_STATUS_UPDATE)
if (!tps6598x_read_data_status(tps))
- goto err_clear_ints;
+ goto err_unlock;
/* Handle plug insert or removal */
if (event & APPLE_CD_REG_INT_PLUG_EVENT)
tps6598x_handle_plug_event(tps, status);
-err_clear_ints:
- tps6598x_write64(tps, TPS_REG_INT_CLEAR1, event);
-
err_unlock:
mutex_unlock(&tps->lock);
@@ -648,25 +651,24 @@ static irqreturn_t tps6598x_interrupt(int irq, void *data)
if (!(event1 | event2))
goto err_unlock;
+ tps6598x_write64(tps, TPS_REG_INT_CLEAR1, event1);
+ tps6598x_write64(tps, TPS_REG_INT_CLEAR2, event2);
+
if (!tps6598x_read_status(tps, &status))
- goto err_clear_ints;
+ goto err_unlock;
if ((event1 | event2) & TPS_REG_INT_POWER_STATUS_UPDATE)
if (!tps6598x_read_power_status(tps))
- goto err_clear_ints;
+ goto err_unlock;
if ((event1 | event2) & TPS_REG_INT_DATA_STATUS_UPDATE)
if (!tps6598x_read_data_status(tps))
- goto err_clear_ints;
+ goto err_unlock;
/* Handle plug insert or removal */
if ((event1 | event2) & TPS_REG_INT_PLUG_EVENT)
tps6598x_handle_plug_event(tps, status);
-err_clear_ints:
- tps6598x_write64(tps, TPS_REG_INT_CLEAR1, event1);
- tps6598x_write64(tps, TPS_REG_INT_CLEAR2, event2);
-
err_unlock:
mutex_unlock(&tps->lock);
diff --git a/drivers/watchdog/apple_wdt.c b/drivers/watchdog/apple_wdt.c
index d4f7399..d2477c2 100644
--- a/drivers/watchdog/apple_wdt.c
+++ b/drivers/watchdog/apple_wdt.c
@@ -192,7 +192,7 @@ static int apple_wdt_probe(struct platform_device *pdev)
return devm_watchdog_register_device(dev, &wdt->wdd);
}
-static int apple_wdt_resume(struct device *dev)
+static int __maybe_unused apple_wdt_resume(struct device *dev)
{
struct apple_wdt *wdt = dev_get_drvdata(dev);
@@ -202,7 +202,7 @@ static int apple_wdt_resume(struct device *dev)
return 0;
}
-static int apple_wdt_suspend(struct device *dev)
+static int __maybe_unused apple_wdt_suspend(struct device *dev)
{
struct apple_wdt *wdt = dev_get_drvdata(dev);
diff --git a/include/linux/fwnode.h b/include/linux/fwnode.h
index 2a72f55..1a1c3e5 100644
--- a/include/linux/fwnode.h
+++ b/include/linux/fwnode.h
@@ -213,5 +213,6 @@ extern bool fw_devlink_is_strict(void);
int fwnode_link_add(struct fwnode_handle *con, struct fwnode_handle *sup);
void fwnode_links_purge(struct fwnode_handle *fwnode);
void fw_devlink_purge_absent_suppliers(struct fwnode_handle *fwnode);
+int fw_devlink_count_absent_consumers(struct fwnode_handle *fwnode);
#endif
diff --git a/include/linux/hid.h b/include/linux/hid.h
index 5a8387a..734b8f3 100644
--- a/include/linux/hid.h
+++ b/include/linux/hid.h
@@ -587,7 +587,9 @@ struct hid_input {
enum hid_type {
HID_TYPE_OTHER = 0,
HID_TYPE_USBMOUSE,
- HID_TYPE_USBNONE
+ HID_TYPE_USBNONE,
+ HID_TYPE_SPI_KEYBOARD,
+ HID_TYPE_SPI_MOUSE,
};
enum hid_battery_status {
@@ -742,6 +744,8 @@ struct hid_descriptor {
.bus = BUS_BLUETOOTH, .vendor = (ven), .product = (prod)
#define HID_I2C_DEVICE(ven, prod) \
.bus = BUS_I2C, .vendor = (ven), .product = (prod)
+#define HID_SPI_DEVICE(ven, prod) \
+ .bus = BUS_SPI, .vendor = (ven), .product = (prod)
#define HID_REPORT_ID(rep) \
.report_type = (rep)
diff --git a/include/linux/io-pgtable.h b/include/linux/io-pgtable.h
index 25142a0..2e126b7 100644
--- a/include/linux/io-pgtable.h
+++ b/include/linux/io-pgtable.h
@@ -143,6 +143,7 @@ struct io_pgtable_cfg {
struct {
u64 ttbr[4];
u32 n_ttbrs;
+ u32 n_levels;
} apple_dart_cfg;
};
};
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index ec289c1..521b6d5 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -164,12 +164,18 @@ enum iommu_resv_type {
IOMMU_RESV_MSI,
/* Software-managed MSI translation window */
IOMMU_RESV_SW_MSI,
+ /*
+ * Memory regions which must be mapped with the specified mapping
+ * at all times.
+ */
+ IOMMU_RESV_TRANSLATED,
};
/**
* struct iommu_resv_region - descriptor for a reserved memory region
* @list: Linked list pointers
* @start: System physical start address of the region
+ * @start: Device virtual start address of the region for IOMMU_RESV_TRANSLATED
* @length: Length of the region in bytes
* @prot: IOMMU Protection flags (READ/WRITE/...)
* @type: Type of the reserved region
@@ -178,6 +184,7 @@ enum iommu_resv_type {
struct iommu_resv_region {
struct list_head list;
phys_addr_t start;
+ dma_addr_t dva;
size_t length;
int prot;
enum iommu_resv_type type;
@@ -616,6 +623,9 @@ extern bool iommu_default_passthrough(void);
extern struct iommu_resv_region *
iommu_alloc_resv_region(phys_addr_t start, size_t length, int prot,
enum iommu_resv_type type, gfp_t gfp);
+extern struct iommu_resv_region *
+iommu_alloc_resv_region_tr(phys_addr_t start, dma_addr_t dva_start, size_t length,
+ int prot, enum iommu_resv_type type, gfp_t gfp);
extern int iommu_get_group_resv_regions(struct iommu_group *group,
struct list_head *head);
diff --git a/include/linux/mfd/macsmc.h b/include/linux/mfd/macsmc.h
new file mode 100644
index 0000000..30bcac1
--- /dev/null
+++ b/include/linux/mfd/macsmc.h
@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple SMC core definitions
+ * Copyright (C) The Asahi Linux Contributors
+ */
+
+#ifndef _LINUX_MFD_MACSMC_H
+#define _LINUX_MFD_MACSMC_H
+
+struct apple_smc;
+
+typedef u32 smc_key;
+
+#define SMC_KEY(s) (smc_key)(_SMC_KEY(#s))
+#define _SMC_KEY(s) (((s)[0] << 24) | ((s)[1] << 16) | ((s)[2] << 8) | (s)[3])
+
+#define APPLE_SMC_READABLE BIT(7)
+#define APPLE_SMC_WRITABLE BIT(6)
+#define APPLE_SMC_FUNCTION BIT(4)
+
+struct apple_smc_key_info {
+ u8 size;
+ u32 type_code;
+ u8 flags;
+};
+
+int apple_smc_read(struct apple_smc *smc, smc_key key, void *buf, size_t size);
+int apple_smc_write(struct apple_smc *smc, smc_key key, void *buf, size_t size);
+int apple_smc_write_atomic(struct apple_smc *smc, smc_key key, void *buf, size_t size);
+int apple_smc_rw(struct apple_smc *smc, smc_key key, void *wbuf, size_t wsize,
+ void *rbuf, size_t rsize);
+
+int apple_smc_get_key_count(struct apple_smc *smc);
+int apple_smc_find_first_key_index(struct apple_smc *smc, smc_key key);
+int apple_smc_get_key_by_index(struct apple_smc *smc, int index, smc_key *key);
+int apple_smc_get_key_info(struct apple_smc *smc, smc_key key, struct apple_smc_key_info *info);
+
+static inline bool apple_smc_key_exists(struct apple_smc *smc, smc_key key)
+{
+ return apple_smc_get_key_info(smc, key, NULL) >= 0;
+}
+
+#define APPLE_SMC_TYPE_OPS(type) \
+ static inline int apple_smc_read_##type(struct apple_smc *smc, smc_key key, type *p) \
+ { \
+ int ret = apple_smc_read(smc, key, p, sizeof(*p)); \
+ return (ret < 0) ? ret : ((ret != sizeof(*p)) ? -EINVAL : 0); \
+ } \
+ static inline int apple_smc_write_##type(struct apple_smc *smc, smc_key key, type p) \
+ { \
+ return apple_smc_write(smc, key, &p, sizeof(p)); \
+ } \
+ static inline int apple_smc_write_##type##_atomic(struct apple_smc *smc, smc_key key, type p) \
+ { \
+ return apple_smc_write_atomic(smc, key, &p, sizeof(p)); \
+ } \
+ static inline int apple_smc_rw_##type(struct apple_smc *smc, smc_key key, \
+ type w, type *r) \
+ { \
+ int ret = apple_smc_rw(smc, key, &w, sizeof(w), r, sizeof(*r)); \
+ return (ret < 0) ? ret : ((ret != sizeof(*r)) ? -EINVAL : 0); \
+ }
+
+APPLE_SMC_TYPE_OPS(u64)
+APPLE_SMC_TYPE_OPS(u32)
+APPLE_SMC_TYPE_OPS(u16)
+APPLE_SMC_TYPE_OPS(u8)
+APPLE_SMC_TYPE_OPS(s64)
+APPLE_SMC_TYPE_OPS(s32)
+APPLE_SMC_TYPE_OPS(s16)
+APPLE_SMC_TYPE_OPS(s8)
+
+static inline int apple_smc_read_flag(struct apple_smc *smc, smc_key key)
+{
+ u8 val;
+ int ret = apple_smc_read_u8(smc, key, &val);
+ if (ret < 0)
+ return ret;
+ return val ? 1 : 0;
+}
+#define apple_smc_write_flag apple_smc_write_u8
+
+int apple_smc_read_f32_scaled(struct apple_smc *smc, smc_key key, int *p, int scale);
+
+int apple_smc_register_notifier(struct apple_smc *smc, struct notifier_block *n);
+int apple_smc_unregister_notifier(struct apple_smc *smc, struct notifier_block *n);
+
+#endif
diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h
index 34663d0..c19f3e0 100644
--- a/include/linux/pm_domain.h
+++ b/include/linux/pm_domain.h
@@ -65,6 +65,13 @@
* GENPD_FLAG_OPP_TABLE_FW: The genpd provider supports performance states,
* but its corresponding OPP tables are not
* described in DT, but are given directly by FW.
+ *
+ * GENPD_FLAG_DEFER_OFF: Defer powerdown if there are any consumer
+ * device fwlinks indicating that some consumer
+ * devices have not yet probed. This is useful
+ * for power domains which are active at boot and
+ * must not be shut down until all consumers
+ * complete their probe sequence.
*/
#define GENPD_FLAG_PM_CLK (1U << 0)
#define GENPD_FLAG_IRQ_SAFE (1U << 1)
@@ -74,6 +81,7 @@
#define GENPD_FLAG_RPM_ALWAYS_ON (1U << 5)
#define GENPD_FLAG_MIN_RESIDENCY (1U << 6)
#define GENPD_FLAG_OPP_TABLE_FW (1U << 7)
+#define GENPD_FLAG_DEFER_OFF (1U << 8)
enum gpd_status {
GENPD_STATE_ON = 0, /* PM domain is on */
diff --git a/include/linux/soc/apple/dockchannel.h b/include/linux/soc/apple/dockchannel.h
new file mode 100644
index 0000000..0b70939
--- /dev/null
+++ b/include/linux/soc/apple/dockchannel.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0-only OR MIT */
+/*
+ * Apple Dockchannel devices
+ * Copyright (C) The Asahi Linux Contributors
+ */
+#ifndef _LINUX_APPLE_DOCKCHANNEL_H_
+#define _LINUX_APPLE_DOCKCHANNEL_H_
+
+#include <linux/device.h>
+#include <linux/types.h>
+#include <linux/of_platform.h>
+
+#if IS_ENABLED(CONFIG_APPLE_DOCKCHANNEL)
+
+struct dockchannel;
+
+struct dockchannel *dockchannel_init(struct platform_device *pdev);
+
+int dockchannel_send(struct dockchannel *dockchannel, const void *buf, size_t count);
+int dockchannel_recv(struct dockchannel *dockchannel, void *buf, size_t count);
+int dockchannel_await(struct dockchannel *dockchannel,
+ void (*callback)(void *cookie, size_t avail),
+ void *cookie, size_t count);
+
+#endif
+#endif
diff --git a/include/linux/soc/apple/rtkit.h b/include/linux/soc/apple/rtkit.h
index fc456f7..c265ea4 100644
--- a/include/linux/soc/apple/rtkit.h
+++ b/include/linux/soc/apple/rtkit.h
@@ -78,6 +78,13 @@ struct apple_rtkit;
struct apple_rtkit *devm_apple_rtkit_init(struct device *dev, void *cookie,
const char *mbox_name, int mbox_idx,
const struct apple_rtkit_ops *ops);
+/*
+ * Frees internal RTKit state allocated by devm_apple_rtkit_init().
+ *
+ * @dev: Pointer to the device node this coprocessor is assocated with
+ * @rtk: Internal RTKit state initialized by devm_apple_rtkit_init()
+ */
+void devm_apple_rtkit_free(struct device *dev, struct apple_rtkit *rtk);
/*
* Non-devm version of devm_apple_rtkit_init. Must be freed with
@@ -161,24 +168,6 @@ int apple_rtkit_send_message(struct apple_rtkit *rtk, u8 ep, u64 message,
struct completion *completion, bool atomic);
/*
- * Send a message to the given endpoint and wait until it has been submitted
- * to the hardware FIFO.
- * Will return zero on success and a negative error code on failure
- * (e.g. -ETIME when the message couldn't be written within the given
- * timeout)
- *
- * @rtk: RTKit reference
- * @ep: target endpoint
- * @message: message to be sent
- * @timeout: timeout in milliseconds to allow the message transmission
- * to be completed
- * @atomic: if set to true this function can be called from atomic
- * context.
- */
-int apple_rtkit_send_message_wait(struct apple_rtkit *rtk, u8 ep, u64 message,
- unsigned long timeout, bool atomic);
-
-/*
* Process incoming messages in atomic context.
* This only guarantees that messages arrive as far as the recv_message_early
* callback; drivers expecting to handle incoming messages synchronously
diff --git a/include/sound/cs42l42.h b/include/sound/cs42l42.h
index 3994e93..dcd52d6 100644
--- a/include/sound/cs42l42.h
+++ b/include/sound/cs42l42.h
@@ -62,6 +62,10 @@
#define CS42L42_INTERNAL_FS_MASK (1 << CS42L42_INTERNAL_FS_SHIFT)
#define CS42L42_SFTRAMP_RATE (CS42L42_PAGE_10 + 0x0A)
+#define CS42L42_SFTRAMP_ASR_RATE_MASK GENMASK(7, 4)
+#define CS42L42_SFTRAMP_ASR_RATE_SHIFT 4
+#define CS42L42_SFTRAMP_DSR_RATE_MASK GENMASK(3, 0)
+#define CS42L42_SFTRAMP_DSR_RATE_SHIFT 0
#define CS42L42_SLOW_START_ENABLE (CS42L42_PAGE_10 + 0x0B)
#define CS42L42_SLOW_START_EN_MASK GENMASK(6, 4)
#define CS42L42_SLOW_START_EN_SHIFT 4
diff --git a/include/sound/soc-card.h b/include/sound/soc-card.h
index ecc02e95..ef46cac 100644
--- a/include/sound/soc-card.h
+++ b/include/sound/soc-card.h
@@ -44,7 +44,7 @@ int snd_soc_card_resume_post(struct snd_soc_card *card);
int snd_soc_card_probe(struct snd_soc_card *card);
int snd_soc_card_late_probe(struct snd_soc_card *card);
-void snd_soc_card_fixup_controls(struct snd_soc_card *card);
+int snd_soc_card_fixup_controls(struct snd_soc_card *card);
int snd_soc_card_remove(struct snd_soc_card *card);
int snd_soc_card_set_bias_level(struct snd_soc_card *card,
diff --git a/include/sound/soc.h b/include/sound/soc.h
index 7792c39..b433365 100644
--- a/include/sound/soc.h
+++ b/include/sound/soc.h
@@ -587,6 +587,10 @@ int snd_soc_get_volsw_range(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
int snd_soc_limit_volume(struct snd_soc_card *card,
const char *name, int max);
+int snd_soc_deactivate_kctl(struct snd_soc_card *card,
+ const char *name, int active);
+int snd_soc_set_enum_kctl(struct snd_soc_card *card,
+ const char *name, const char *strval);
int snd_soc_bytes_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo);
int snd_soc_bytes_get(struct snd_kcontrol *kcontrol,
@@ -955,7 +959,7 @@ struct snd_soc_card {
int (*probe)(struct snd_soc_card *card);
int (*late_probe)(struct snd_soc_card *card);
- void (*fixup_controls)(struct snd_soc_card *card);
+ int (*fixup_controls)(struct snd_soc_card *card);
int (*remove)(struct snd_soc_card *card);
/* the pre and post PM functions are used to do any PM work before and
diff --git a/init/Kconfig b/init/Kconfig
index 9ffb103..8f15ce4 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -1885,9 +1885,9 @@
bool "Rust support"
depends on HAVE_RUST
depends on RUST_IS_AVAILABLE
+ depends on RANDSTRUCT_NONE
depends on !MODVERSIONS
- depends on !GCC_PLUGINS
- depends on !RANDSTRUCT
+ depends on !GCC_PLUGIN_RANDSTRUCT
depends on !DEBUG_INFO_BTF || PAHOLE_HAS_LANG_EXCLUDE
select CONSTRUCTORS
help
diff --git a/lib/vsprintf.c b/lib/vsprintf.c
index 3e3733a..d6c7e8e 100644
--- a/lib/vsprintf.c
+++ b/lib/vsprintf.c
@@ -1760,27 +1760,50 @@ char *fourcc_string(char *buf, char *end, const u32 *fourcc,
char output[sizeof("0123 little-endian (0x01234567)")];
char *p = output;
unsigned int i;
+ bool pix_fmt = false;
u32 orig, val;
- if (fmt[1] != 'c' || fmt[2] != 'c')
+ if (fmt[1] != 'c')
return error_string(buf, end, "(%p4?)", spec);
if (check_pointer(&buf, end, fourcc, spec))
return buf;
orig = get_unaligned(fourcc);
- val = orig & ~BIT(31);
+ switch (fmt[2]) {
+ case 'h':
+ val = orig;
+ break;
+ case 'r':
+ val = orig = swab32(orig);
+ break;
+ case 'l':
+ val = orig = le32_to_cpu(orig);
+ break;
+ case 'b':
+ val = orig = be32_to_cpu(orig);
+ break;
+ case 'c':
+ /* Pixel formats are printed LSB-first */
+ val = swab32(orig & ~BIT(31));
+ pix_fmt = true;
+ break;
+ default:
+ return error_string(buf, end, "(%p4?)", spec);
+ }
for (i = 0; i < sizeof(u32); i++) {
- unsigned char c = val >> (i * 8);
+ unsigned char c = val >> ((3 - i) * 8);
/* Print non-control ASCII characters as-is, dot otherwise */
*p++ = isascii(c) && isprint(c) ? c : '.';
}
- *p++ = ' ';
- strcpy(p, orig & BIT(31) ? "big-endian" : "little-endian");
- p += strlen(p);
+ if (pix_fmt) {
+ *p++ = ' ';
+ strcpy(p, orig & BIT(31) ? "big-endian" : "little-endian");
+ p += strlen(p);
+ }
*p++ = ' ';
*p++ = '(';
diff --git a/localversion.05-asahi b/localversion.05-asahi
new file mode 100644
index 0000000..6742ba7
--- /dev/null
+++ b/localversion.05-asahi
@@ -0,0 +1 @@
+-asahi
diff --git a/sound/soc/apple/Kconfig b/sound/soc/apple/Kconfig
index 793f778..992e416 100644
--- a/sound/soc/apple/Kconfig
+++ b/sound/soc/apple/Kconfig
@@ -6,3 +6,20 @@
help
This option enables an ASoC platform driver for MCA peripherals found
on Apple Silicon SoCs.
+
+config SND_SOC_APPLE_MACAUDIO
+ tristate "Sound support for Apple Silicon Macs"
+ depends on ARCH_APPLE || COMPILE_TEST
+ select SND_SOC_APPLE_MCA
+ select SND_SIMPLE_CARD_UTILS
+ select APPLE_ADMAC
+ select COMMON_CLK_APPLE_NCO
+ select SND_SOC_TAS2764
+ select SND_SOC_TAS2770
+ select SND_SOC_CS42L83
+ select SND_SOC_CS42L84
+ default ARCH_APPLE
+ help
+ This option enables an ASoC machine-level driver for Apple Silicon Macs
+ and it also enables the required SoC and codec drivers for overall
+ sound support on these machines.
diff --git a/sound/soc/apple/Makefile b/sound/soc/apple/Makefile
index 7a30bf4..a14b8fc 100644
--- a/sound/soc/apple/Makefile
+++ b/sound/soc/apple/Makefile
@@ -1,3 +1,7 @@
snd-soc-apple-mca-objs := mca.o
obj-$(CONFIG_SND_SOC_APPLE_MCA) += snd-soc-apple-mca.o
+
+snd-soc-macaudio-objs := macaudio.o
+
+obj-$(CONFIG_SND_SOC_APPLE_MACAUDIO) += snd-soc-macaudio.o
diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
new file mode 100644
index 0000000..0e2844c
--- /dev/null
+++ b/sound/soc/apple/macaudio.c
@@ -0,0 +1,1025 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ASoC machine driver for Apple Silicon Macs
+ *
+ * Copyright (C) The Asahi Linux Contributors
+ *
+ * Based on sound/soc/qcom/{sc7180.c|common.c}
+ * Copyright (c) 2018, Linaro Limited.
+ * Copyright (c) 2020, The Linux Foundation. All rights reserved.
+ *
+ *
+ * The platform driver has independent frontend and backend DAIs with the
+ * option of routing backends to any of the frontends. The platform
+ * driver configures the routing based on DPCM couplings in ASoC runtime
+ * structures, which in turn are determined from DAPM paths by ASoC. But the
+ * platform driver doesn't supply relevant DAPM paths and leaves that up for
+ * the machine driver to fill in. The filled-in virtual topology can be
+ * anything as long as any backend isn't connected to more than one frontend
+ * at any given time. (The limitation is due to the unsupported case of
+ * reparenting of live BEs.)
+ */
+
+#define DEBUG
+
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/simple_card_utils.h>
+#include <sound/soc.h>
+#include <sound/soc-jack.h>
+#include <uapi/linux/input-event-codes.h>
+
+#define DRIVER_NAME "snd-soc-macaudio"
+
+/*
+ * CPU side is bit and frame clock provider
+ * I2S has both clocks inverted
+ */
+#define MACAUDIO_DAI_FMT (SND_SOC_DAIFMT_I2S | \
+ SND_SOC_DAIFMT_CBC_CFC | \
+ SND_SOC_DAIFMT_GATED | \
+ SND_SOC_DAIFMT_IB_IF)
+#define MACAUDIO_JACK_MASK (SND_JACK_HEADSET | SND_JACK_HEADPHONE)
+#define MACAUDIO_SLOTWIDTH 32
+
+struct macaudio_snd_data {
+ struct snd_soc_card card;
+ struct snd_soc_jack jack;
+ int jack_plugin_state;
+
+ bool has_speakers;
+ unsigned int max_channels;
+
+ struct macaudio_link_props {
+ /* frontend props */
+ unsigned int bclk_ratio;
+
+ /* backend props */
+ bool is_speakers;
+ bool is_headphones;
+ unsigned int tdm_mask;
+ } *link_props;
+
+ unsigned int speaker_nchans_array[2];
+ struct snd_pcm_hw_constraint_list speaker_nchans_list;
+};
+
+static bool please_blow_up_my_speakers;
+module_param(please_blow_up_my_speakers, bool, 0644);
+MODULE_PARM_DESC(please_blow_up_my_speakers, "Allow unsafe or untested operating configurations");
+
+SND_SOC_DAILINK_DEFS(primary,
+ DAILINK_COMP_ARRAY(COMP_CPU("mca-pcm-0")), // CPU
+ DAILINK_COMP_ARRAY(COMP_DUMMY()), // CODEC
+ DAILINK_COMP_ARRAY(COMP_EMPTY())); // platform (filled at runtime)
+
+SND_SOC_DAILINK_DEFS(secondary,
+ DAILINK_COMP_ARRAY(COMP_CPU("mca-pcm-1")), // CPU
+ DAILINK_COMP_ARRAY(COMP_DUMMY()), // CODEC
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link macaudio_fe_links[] = {
+ {
+ .name = "Primary",
+ .stream_name = "Primary",
+ .dynamic = 1,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .dpcm_merged_rate = 1,
+ .dpcm_merged_chan = 1,
+ .dpcm_merged_format = 1,
+ .dai_fmt = MACAUDIO_DAI_FMT,
+ SND_SOC_DAILINK_REG(primary),
+ },
+ {
+ .name = "Secondary",
+ .stream_name = "Secondary",
+ .dynamic = 1,
+ .dpcm_playback = 1,
+ .dpcm_merged_rate = 1,
+ .dpcm_merged_chan = 1,
+ .dpcm_merged_format = 1,
+ .dai_fmt = MACAUDIO_DAI_FMT,
+ SND_SOC_DAILINK_REG(secondary),
+ },
+};
+
+static struct macaudio_link_props macaudio_fe_link_props[] = {
+ {
+ /*
+ * Primary FE
+ *
+ * The bclk ratio at 64 for the primary frontend is important
+ * to ensure that the headphones codec's idea of left and right
+ * in a stereo stream over I2S fits in nicely with everyone else's.
+ * (This is until the headphones codec's driver supports
+ * set_tdm_slot.)
+ *
+ * The low bclk ratio precludes transmitting more than two
+ * channels over I2S, but that's okay since there is the secondary
+ * FE for speaker arrays anyway.
+ */
+ .bclk_ratio = 64,
+ },
+ {
+ /*
+ * Secondary FE
+ *
+ * Here we want frames plenty long to be able to drive all
+ * those fancy speaker arrays.
+ */
+ .bclk_ratio = 256,
+ }
+};
+
+static int macaudio_copy_link(struct device *dev, struct snd_soc_dai_link *target,
+ struct snd_soc_dai_link *source)
+{
+ memcpy(target, source, sizeof(struct snd_soc_dai_link));
+
+ target->cpus = devm_kmemdup(dev, target->cpus,
+ sizeof(*target->cpus) * target->num_cpus,
+ GFP_KERNEL);
+ target->codecs = devm_kmemdup(dev, target->codecs,
+ sizeof(*target->codecs) * target->num_codecs,
+ GFP_KERNEL);
+ target->platforms = devm_kmemdup(dev, target->platforms,
+ sizeof(*target->platforms) * target->num_platforms,
+ GFP_KERNEL);
+
+ if (!target->cpus || !target->codecs || !target->platforms)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int macaudio_parse_of_component(struct device_node *node, int index,
+ struct snd_soc_dai_link_component *comp)
+{
+ struct of_phandle_args args;
+ int ret;
+
+ ret = of_parse_phandle_with_args(node, "sound-dai", "#sound-dai-cells",
+ index, &args);
+ if (ret)
+ return ret;
+ comp->of_node = args.np;
+ return snd_soc_get_dai_name(&args, &comp->dai_name);
+}
+
+/*
+ * Parse one DPCM backend from the devicetree. This means taking one
+ * of the CPU DAIs and combining it with one or more CODEC DAIs.
+ */
+static int macaudio_parse_of_be_dai_link(struct macaudio_snd_data *ma,
+ struct snd_soc_dai_link *link,
+ int be_index, int ncodecs_per_be,
+ struct device_node *cpu,
+ struct device_node *codec)
+{
+ struct snd_soc_dai_link_component *comp;
+ struct device *dev = ma->card.dev;
+ int codec_base = be_index * ncodecs_per_be;
+ int ret, i;
+
+ link->no_pcm = 1;
+ link->dpcm_playback = 1;
+ link->dpcm_capture = 1;
+
+ link->dai_fmt = MACAUDIO_DAI_FMT;
+
+ link->num_codecs = ncodecs_per_be;
+ link->codecs = devm_kcalloc(dev, ncodecs_per_be,
+ sizeof(*comp), GFP_KERNEL);
+ link->num_cpus = 1;
+ link->cpus = devm_kzalloc(dev, sizeof(*comp), GFP_KERNEL);
+
+ if (!link->codecs || !link->cpus)
+ return -ENOMEM;
+
+ link->num_platforms = 0;
+
+ for_each_link_codecs(link, i, comp) {
+ ret = macaudio_parse_of_component(codec, codec_base + i, comp);
+ if (ret)
+ return ret;
+ }
+
+ ret = macaudio_parse_of_component(cpu, be_index, link->cpus);
+ if (ret)
+ return ret;
+
+ link->name = link->cpus[0].dai_name;
+
+ return 0;
+}
+
+static int macaudio_parse_of(struct macaudio_snd_data *ma)
+{
+ struct device_node *codec = NULL;
+ struct device_node *cpu = NULL;
+ struct device_node *np = NULL;
+ struct device_node *platform = NULL;
+ struct snd_soc_dai_link *link = NULL;
+ struct snd_soc_card *card = &ma->card;
+ struct device *dev = card->dev;
+ struct macaudio_link_props *link_props;
+ int ret, num_links, i;
+
+ ret = snd_soc_of_parse_card_name(card, "model");
+ if (ret) {
+ dev_err(dev, "Error parsing card name: %d\n", ret);
+ return ret;
+ }
+
+ /* Populate links, start with the fixed number of FE links */
+ num_links = ARRAY_SIZE(macaudio_fe_links);
+
+ /* Now add together the (dynamic) number of BE links */
+ for_each_available_child_of_node(dev->of_node, np) {
+ int num_cpus;
+
+ cpu = of_get_child_by_name(np, "cpu");
+ if (!cpu) {
+ dev_err(dev, "missing CPU DAI node at %pOF\n", np);
+ ret = -EINVAL;
+ goto err_free;
+ }
+
+ num_cpus = of_count_phandle_with_args(cpu, "sound-dai",
+ "#sound-dai-cells");
+
+ if (num_cpus <= 0) {
+ dev_err(card->dev, "missing sound-dai property at %pOF\n", cpu);
+ ret = -EINVAL;
+ goto err_free;
+ }
+ of_node_put(cpu);
+ cpu = NULL;
+
+ /* Each CPU specified counts as one BE link */
+ num_links += num_cpus;
+ }
+
+ /* Allocate the DAI link array */
+ card->dai_link = devm_kcalloc(dev, num_links, sizeof(*link), GFP_KERNEL);
+ ma->link_props = devm_kcalloc(dev, num_links, sizeof(*ma->link_props), GFP_KERNEL);
+ if (!card->dai_link || !ma->link_props)
+ return -ENOMEM;
+
+ card->num_links = num_links;
+ link = card->dai_link;
+ link_props = ma->link_props;
+
+ for (i = 0; i < ARRAY_SIZE(macaudio_fe_links); i++) {
+ ret = macaudio_copy_link(dev, link, &macaudio_fe_links[i]);
+ if (ret)
+ goto err_free;
+
+ memcpy(link_props, &macaudio_fe_link_props[i], sizeof(struct macaudio_link_props));
+ link++; link_props++;
+ }
+
+ for (i = 0; i < num_links; i++)
+ card->dai_link[i].id = i;
+
+ /* Fill in the BEs */
+ for_each_available_child_of_node(dev->of_node, np) {
+ const char *link_name;
+ bool speakers;
+ int be_index, num_codecs, num_bes, ncodecs_per_cpu, nchannels;
+ unsigned int left_mask, right_mask;
+
+ ret = of_property_read_string(np, "link-name", &link_name);
+ if (ret) {
+ dev_err(card->dev, "missing link name\n");
+ goto err_free;
+ }
+
+ speakers = !strcmp(link_name, "Speaker")
+ || !strcmp(link_name, "Speakers");
+ if (speakers)
+ ma->has_speakers = 1;
+
+ cpu = of_get_child_by_name(np, "cpu");
+ codec = of_get_child_by_name(np, "codec");
+
+ if (!codec || !cpu) {
+ dev_err(dev, "missing DAI specifications for '%s'\n", link_name);
+ ret = -EINVAL;
+ goto err_free;
+ }
+
+ num_bes = of_count_phandle_with_args(cpu, "sound-dai",
+ "#sound-dai-cells");
+ if (num_bes <= 0) {
+ dev_err(card->dev, "missing sound-dai property at %pOF\n", cpu);
+ ret = -EINVAL;
+ goto err_free;
+ }
+
+ num_codecs = of_count_phandle_with_args(codec, "sound-dai",
+ "#sound-dai-cells");
+ if (num_codecs <= 0) {
+ dev_err(card->dev, "missing sound-dai property at %pOF\n", codec);
+ ret = -EINVAL;
+ goto err_free;
+ }
+
+ if (num_codecs % num_bes != 0) {
+ dev_err(card->dev, "bad combination of CODEC (%d) and CPU (%d) number at %pOF\n",
+ num_codecs, num_bes, np);
+ ret = -EINVAL;
+ goto err_free;
+ }
+
+ /*
+ * Now parse the cpu/codec lists into a number of DPCM backend links.
+ * In each link there will be one DAI from the cpu list paired with
+ * an evenly distributed number of DAIs from the codec list. (As is
+ * the binding semantics.)
+ */
+ ncodecs_per_cpu = num_codecs / num_bes;
+ nchannels = num_codecs * (speakers ? 1 : 2);
+
+ /* Save the max number of channels on the platform */
+ if (nchannels > ma->max_channels)
+ ma->max_channels = nchannels;
+
+ /*
+ * If there is a single speaker, assign two channels to it, because
+ * it can do downmix.
+ */
+ if (nchannels < 2)
+ nchannels = 2;
+
+ left_mask = 0;
+ for (i = 0; i < nchannels; i += 2)
+ left_mask = left_mask << 2 | 1;
+ right_mask = left_mask << 1;
+
+ for (be_index = 0; be_index < num_bes; be_index++) {
+ ret = macaudio_parse_of_be_dai_link(ma, link, be_index,
+ ncodecs_per_cpu, cpu, codec);
+ if (ret)
+ goto err_free;
+
+ link_props->is_speakers = speakers;
+ link_props->is_headphones = !speakers;
+
+ if (num_bes == 2)
+ /* This sound peripheral is split between left and right BE */
+ link_props->tdm_mask = be_index ? right_mask : left_mask;
+ else
+ /* One BE covers all of the peripheral */
+ link_props->tdm_mask = left_mask | right_mask;
+
+ /* Steal platform OF reference for use in FE links later */
+ platform = link->cpus->of_node;
+
+ link++; link_props++;
+ }
+
+ of_node_put(codec);
+ of_node_put(cpu);
+ cpu = codec = NULL;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(macaudio_fe_links); i++)
+ card->dai_link[i].platforms->of_node = platform;
+
+ return 0;
+
+err_free:
+ of_node_put(codec);
+ of_node_put(cpu);
+ of_node_put(np);
+
+ if (!card->dai_link)
+ return ret;
+
+ for (i = 0; i < num_links; i++) {
+ /*
+ * TODO: If we don't go through this path are the references
+ * freed inside ASoC?
+ */
+ snd_soc_of_put_dai_link_codecs(&card->dai_link[i]);
+ snd_soc_of_put_dai_link_cpus(&card->dai_link[i]);
+ }
+
+ return ret;
+}
+
+static int macaudio_get_runtime_bclk_ratio(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dpcm *dpcm;
+
+ /*
+ * If this is a FE, look it up in link_props directly.
+ * If this is a BE, look it up in the respective FE.
+ */
+ if (!rtd->dai_link->no_pcm)
+ return ma->link_props[rtd->dai_link->id].bclk_ratio;
+
+ for_each_dpcm_fe(rtd, substream->stream, dpcm) {
+ int fe_id = dpcm->fe->dai_link->id;
+
+ return ma->link_props[fe_id].bclk_ratio;
+ }
+
+ return 0;
+}
+
+static int macaudio_dpcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ int bclk_ratio = macaudio_get_runtime_bclk_ratio(substream);
+ int i;
+
+ if (bclk_ratio) {
+ struct snd_soc_dai *dai;
+ int mclk = params_rate(params) * bclk_ratio;
+
+ for_each_rtd_codec_dais(rtd, i, dai) {
+ snd_soc_dai_set_sysclk(dai, 0, mclk, SND_SOC_CLOCK_IN);
+ snd_soc_dai_set_bclk_ratio(dai, bclk_ratio);
+ }
+
+ snd_soc_dai_set_sysclk(cpu_dai, 0, mclk, SND_SOC_CLOCK_OUT);
+ snd_soc_dai_set_bclk_ratio(cpu_dai, bclk_ratio);
+ }
+
+ return 0;
+}
+
+static int macaudio_fe_startup(struct snd_pcm_substream *substream)
+{
+
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(rtd->card);
+ int ret;
+
+ /* The FEs must never have more channels than the hardware */
+ ret = snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_CHANNELS, 0, ma->max_channels);
+
+ if (ret < 0) {
+ dev_err(rtd->dev, "Failed to constrain FE %d! %d", rtd->dai_link->id, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int macaudio_fe_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_pcm_runtime *be;
+ struct snd_soc_dpcm *dpcm;
+
+ be = NULL;
+ for_each_dpcm_be(rtd, substream->stream, dpcm) {
+ be = dpcm->be;
+ break;
+ }
+
+ if (!be) {
+ dev_err(rtd->dev, "opening PCM device '%s' with no audio route configured (bad settings applied to the sound card)\n",
+ rtd->dai_link->name);
+ return -EINVAL;
+ }
+
+ return macaudio_dpcm_hw_params(substream, params);
+}
+
+
+static void macaudio_dpcm_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct snd_soc_dai *dai;
+ int bclk_ratio = macaudio_get_runtime_bclk_ratio(substream);
+ int i;
+
+ if (bclk_ratio) {
+ for_each_rtd_codec_dais(rtd, i, dai)
+ snd_soc_dai_set_sysclk(dai, 0, 0, SND_SOC_CLOCK_IN);
+
+ snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_OUT);
+ }
+}
+
+static const struct snd_soc_ops macaudio_fe_ops = {
+ .startup = macaudio_fe_startup,
+ .shutdown = macaudio_dpcm_shutdown,
+ .hw_params = macaudio_fe_hw_params,
+};
+
+static const struct snd_soc_ops macaudio_be_ops = {
+ .shutdown = macaudio_dpcm_shutdown,
+ .hw_params = macaudio_dpcm_hw_params,
+};
+
+static int macaudio_be_assign_tdm(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+ struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+ struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id];
+ struct snd_soc_dai *dai;
+ unsigned int mask;
+ int nslots, ret, i;
+
+ if (!props->tdm_mask)
+ return 0;
+
+ mask = props->tdm_mask;
+ nslots = __fls(mask) + 1;
+
+ if (rtd->dai_link->num_codecs == 1) {
+ ret = snd_soc_dai_set_tdm_slot(snd_soc_rtd_to_codec(rtd, 0), mask,
+ 0, nslots, MACAUDIO_SLOTWIDTH);
+
+ /*
+ * Headphones get a pass on -ENOTSUPP (see the comment
+ * around bclk_ratio value for primary FE).
+ */
+ if (ret == -ENOTSUPP && props->is_headphones)
+ return 0;
+
+ return ret;
+ }
+
+ for_each_rtd_codec_dais(rtd, i, dai) {
+ int slot = __ffs(mask);
+
+ mask &= ~(1 << slot);
+ ret = snd_soc_dai_set_tdm_slot(dai, 1 << slot, 0, nslots,
+ MACAUDIO_SLOTWIDTH);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int macaudio_be_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+ struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+ struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id];
+ struct snd_soc_dai *dai;
+ int i, ret;
+
+ ret = macaudio_be_assign_tdm(rtd);
+ if (ret < 0)
+ return ret;
+
+ if (props->is_headphones) {
+ for_each_rtd_codec_dais(rtd, i, dai)
+ snd_soc_component_set_jack(dai->component, &ma->jack, NULL);
+ }
+
+ return 0;
+}
+
+static void macaudio_be_exit(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+ struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+ struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id];
+ struct snd_soc_dai *dai;
+ int i;
+
+ if (props->is_headphones) {
+ for_each_rtd_codec_dais(rtd, i, dai)
+ snd_soc_component_set_jack(dai->component, NULL, NULL);
+ }
+}
+
+static int macaudio_fe_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+ struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+ struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id];
+ int nslots = props->bclk_ratio / MACAUDIO_SLOTWIDTH;
+
+ return snd_soc_dai_set_tdm_slot(snd_soc_rtd_to_cpu(rtd, 0), (1 << nslots) - 1,
+ (1 << nslots) - 1, nslots, MACAUDIO_SLOTWIDTH);
+}
+
+static struct snd_soc_jack_pin macaudio_jack_pins[] = {
+ {
+ .pin = "Headphone",
+ .mask = SND_JACK_HEADPHONE,
+ },
+ {
+ .pin = "Headset Mic",
+ .mask = SND_JACK_MICROPHONE,
+ },
+};
+
+static int macaudio_probe(struct snd_soc_card *card)
+{
+ struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+ int ret;
+
+ dev_dbg(card->dev, "%s!\n", __func__);
+
+ ret = snd_soc_card_jack_new_pins(card, "Headphone Jack",
+ SND_JACK_HEADSET | SND_JACK_HEADPHONE,
+ &ma->jack, macaudio_jack_pins,
+ ARRAY_SIZE(macaudio_jack_pins));
+ if (ret < 0) {
+ dev_err(card->dev, "jack creation failed: %d\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int macaudio_add_backend_dai_route(struct snd_soc_card *card, struct snd_soc_dai *dai,
+ bool is_speakers)
+{
+ struct snd_soc_dapm_route routes[2];
+ struct snd_soc_dapm_route *r;
+ int nroutes = 0;
+ int ret;
+
+ memset(routes, 0, sizeof(routes));
+
+ dev_dbg(card->dev, "adding routes for '%s'\n", dai->name);
+
+ r = &routes[nroutes++];
+ if (is_speakers)
+ r->source = "Speaker Playback";
+ else
+ r->source = "Headphone Playback";
+ r->sink = dai->stream[SNDRV_PCM_STREAM_PLAYBACK].widget->name;
+
+ /* If headphone jack, add capture path */
+ if (!is_speakers) {
+ r = &routes[nroutes++];
+ r->source = dai->stream[SNDRV_PCM_STREAM_CAPTURE].widget->name;
+ r->sink = "Headset Capture";
+ }
+
+ ret = snd_soc_dapm_add_routes(&card->dapm, routes, nroutes);
+ if (ret)
+ dev_err(card->dev, "failed adding dynamic DAPM routes for %s\n",
+ dai->name);
+ return ret;
+}
+
+static int macaudio_add_pin_routes(struct snd_soc_card *card, struct snd_soc_component *component,
+ bool is_speakers)
+{
+ struct snd_soc_dapm_route routes[2];
+ struct snd_soc_dapm_route *r;
+ int nroutes = 0;
+ char buf[32];
+ int ret;
+
+ memset(routes, 0, sizeof(routes));
+
+ /* Connect the far ends of CODECs to pins */
+ if (is_speakers) {
+ r = &routes[nroutes++];
+ r->source = "OUT";
+ if (component->name_prefix) {
+ snprintf(buf, sizeof(buf) - 1, "%s OUT", component->name_prefix);
+ r->source = buf;
+ }
+ r->sink = "Speaker";
+ } else {
+ r = &routes[nroutes++];
+ r->source = "Jack HP";
+ r->sink = "Headphone";
+ r = &routes[nroutes++];
+ r->source = "Headset Mic";
+ r->sink = "Jack HS";
+ }
+
+ ret = snd_soc_dapm_add_routes(&card->dapm, routes, nroutes);
+ if (ret)
+ dev_err(card->dev, "failed adding dynamic DAPM routes for %s\n",
+ component->name);
+ return ret;
+}
+
+static int macaudio_late_probe(struct snd_soc_card *card)
+{
+ struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+ struct snd_soc_pcm_runtime *rtd;
+ struct snd_soc_dai *dai;
+ int ret, i;
+
+ /* Add the dynamic DAPM routes */
+ for_each_card_rtds(card, rtd) {
+ struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id];
+
+ if (!rtd->dai_link->no_pcm)
+ continue;
+
+ for_each_rtd_cpu_dais(rtd, i, dai) {
+ ret = macaudio_add_backend_dai_route(card, dai, props->is_speakers);
+
+ if (ret)
+ return ret;
+ }
+
+ for_each_rtd_codec_dais(rtd, i, dai) {
+ ret = macaudio_add_pin_routes(card, dai->component,
+ props->is_speakers);
+
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+#define CHECK(call, pattern, value) \
+ { \
+ int ret = call(card, pattern, value); \
+ if (ret < 1 && !please_blow_up_my_speakers) { \
+ dev_err(card->dev, "%s on '%s': %d\n", #call, pattern, ret); \
+ return ret; \
+ } \
+ dev_dbg(card->dev, "%s on '%s': %d hits\n", #call, pattern, ret); \
+ }
+
+
+static int macaudio_j274_fixup_controls(struct snd_soc_card *card)
+{
+ struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+
+ if (ma->has_speakers) {
+ CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 14); // 20 set by macOS, this is 3 dB below
+ }
+
+ return 0;
+}
+
+static int macaudio_j313_fixup_controls(struct snd_soc_card *card) {
+ struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+
+ if (ma->has_speakers) {
+ if (!please_blow_up_my_speakers) {
+ dev_err(card->dev, "driver can't assure safety on this model, refusing probe\n");
+ return -EINVAL;
+ }
+
+ CHECK(snd_soc_set_enum_kctl, "* ASI1 Sel", "Left");
+ CHECK(snd_soc_deactivate_kctl, "* ASI1 Sel", 0);
+
+ /* !!! This is copied from j274, not obtained by looking at
+ * what macOS sets.
+ */
+ CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 14);
+
+ /*
+ * Since we don't set the right slots yet to avoid
+ * driver conflict on the I2S bus sending ISENSE/VSENSE
+ * samples from the codecs back to us, disable the
+ * controls.
+ */
+ CHECK(snd_soc_deactivate_kctl, "* VSENSE Switch", 0);
+ CHECK(snd_soc_deactivate_kctl, "* ISENSE Switch", 0);
+ }
+
+ return 0;
+}
+
+static int macaudio_j314_fixup_controls(struct snd_soc_card *card)
+{
+ struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+
+ if (ma->has_speakers) {
+ if (!please_blow_up_my_speakers) {
+ dev_err(card->dev, "driver can't assure safety on this model, refusing probe\n");
+ return -EINVAL;
+ }
+
+ CHECK(snd_soc_set_enum_kctl, "* ASI1 Sel", "Left");
+ CHECK(snd_soc_deactivate_kctl, "* ASI1 Sel", 0);
+ CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 9); // 15 set by macOS, this is 3 dB below
+ CHECK(snd_soc_set_enum_kctl, "* Tweeter HPF Corner Frequency", "800 Hz");
+ CHECK(snd_soc_deactivate_kctl, "* Tweeter HPF Corner Frequency", 0);
+
+ /*
+ * The speaker amps suffer from spurious overcurrent
+ * events on their unmute, so enable autoretry.
+ */
+ CHECK(snd_soc_set_enum_kctl, "* OCE Handling", "Retry");
+ CHECK(snd_soc_deactivate_kctl, "* OCE Handling", 0);
+
+ /*
+ * Since we don't set the right slots yet to avoid
+ * driver conflict on the I2S bus sending ISENSE/VSENSE
+ * samples from the codecs back to us, disable the
+ * controls.
+ */
+ CHECK(snd_soc_deactivate_kctl, "* VSENSE Switch", 0);
+ CHECK(snd_soc_deactivate_kctl, "* ISENSE Switch", 0);
+ }
+
+ return 0;
+}
+
+static int macaudio_j375_fixup_controls(struct snd_soc_card *card)
+{
+ struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+
+ if (ma->has_speakers) {
+ if (!please_blow_up_my_speakers) {
+ dev_err(card->dev, "driver can't assure safety on this model, refusing probe\n");
+ return -EINVAL;
+ }
+
+ CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 14); // 20 set by macOS, this is 3 dB below
+ }
+
+ return 0;
+}
+
+static int macaudio_j493_fixup_controls(struct snd_soc_card *card)
+{
+ struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+
+ if (ma->has_speakers) {
+ if (!please_blow_up_my_speakers) {
+ dev_err(card->dev, "driver can't assure safety on this model, refusing probe\n");
+ return -EINVAL;
+ }
+
+ CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 9); // 15 set by macOS, this is 3 dB below
+ }
+
+ return 0;
+}
+
+static int macaudio_fallback_fixup_controls(struct snd_soc_card *card)
+{
+ struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+
+ if (ma->has_speakers && !please_blow_up_my_speakers) {
+ dev_err(card->dev, "driver can't assure safety on this model, refusing probe\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+#undef CHECK
+
+static const char * const macaudio_spk_mux_texts[] = {
+ "Primary",
+ "Secondary"
+};
+
+SOC_ENUM_SINGLE_VIRT_DECL(macaudio_spk_mux_enum, macaudio_spk_mux_texts);
+
+static const struct snd_kcontrol_new macaudio_spk_mux =
+ SOC_DAPM_ENUM("Speaker Playback Mux", macaudio_spk_mux_enum);
+
+static const char * const macaudio_hp_mux_texts[] = {
+ "Primary",
+ "Secondary"
+};
+
+SOC_ENUM_SINGLE_VIRT_DECL(macaudio_hp_mux_enum, macaudio_hp_mux_texts);
+
+static const struct snd_kcontrol_new macaudio_hp_mux =
+ SOC_DAPM_ENUM("Headphones Playback Mux", macaudio_hp_mux_enum);
+
+static const struct snd_soc_dapm_widget macaudio_snd_widgets[] = {
+ SND_SOC_DAPM_SPK("Speaker", NULL),
+ SND_SOC_DAPM_SPK("Speaker (Static)", NULL),
+ SND_SOC_DAPM_HP("Headphone", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+
+ SND_SOC_DAPM_MUX("Speaker Playback Mux", SND_SOC_NOPM, 0, 0, &macaudio_spk_mux),
+ SND_SOC_DAPM_MUX("Headphone Playback Mux", SND_SOC_NOPM, 0, 0, &macaudio_hp_mux),
+
+ SND_SOC_DAPM_AIF_OUT("Speaker Playback", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("Headphone Playback", NULL, 0, SND_SOC_NOPM, 0, 0),
+
+ SND_SOC_DAPM_AIF_IN("Headset Capture", NULL, 0, SND_SOC_NOPM, 0, 0),
+};
+
+static const struct snd_kcontrol_new macaudio_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Speaker"),
+ SOC_DAPM_PIN_SWITCH("Headphone"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+};
+
+static const struct snd_soc_dapm_route macaudio_dapm_routes[] = {
+ /* Playback paths */
+ { "Speaker Playback Mux", "Primary", "PCM0 TX" },
+ { "Speaker Playback Mux", "Secondary", "PCM1 TX" },
+ { "Speaker Playback", NULL, "Speaker Playback Mux"},
+
+ { "Headphone Playback Mux", "Primary", "PCM0 TX" },
+ { "Headphone Playback Mux", "Secondary", "PCM1 TX" },
+ { "Headphone Playback", NULL, "Headphone Playback Mux"},
+ /*
+ * Additional paths (to specific I2S ports) are added dynamically.
+ */
+
+ /* Capture paths */
+ { "PCM0 RX", NULL, "Headset Capture" },
+};
+
+static const struct of_device_id macaudio_snd_device_id[] = {
+ { .compatible = "apple,j274-macaudio", .data = macaudio_j274_fixup_controls },
+ { .compatible = "apple,j313-macaudio", .data = macaudio_j313_fixup_controls },
+ { .compatible = "apple,j314-macaudio", .data = macaudio_j314_fixup_controls },
+ { .compatible = "apple,j375-macaudio", .data = macaudio_j375_fixup_controls },
+ { .compatible = "apple,j413-macaudio", .data = macaudio_j314_fixup_controls },
+ { .compatible = "apple,j415-macaudio", .data = macaudio_j314_fixup_controls },
+ { .compatible = "apple,j493-macaudio", .data = macaudio_j493_fixup_controls },
+ { .compatible = "apple,macaudio"},
+ { }
+};
+MODULE_DEVICE_TABLE(of, macaudio_snd_device_id);
+
+static int macaudio_snd_platform_probe(struct platform_device *pdev)
+{
+ struct snd_soc_card *card;
+ struct macaudio_snd_data *data;
+ struct device *dev = &pdev->dev;
+ struct snd_soc_dai_link *link;
+ const struct of_device_id *of_id;
+ int ret;
+ int i;
+
+ of_id = of_match_device(macaudio_snd_device_id, dev);
+ if (!of_id)
+ return -EINVAL;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+ card = &data->card;
+ snd_soc_card_set_drvdata(card, data);
+
+ card->owner = THIS_MODULE;
+ card->driver_name = "macaudio";
+ card->dev = dev;
+ card->dapm_widgets = macaudio_snd_widgets;
+ card->num_dapm_widgets = ARRAY_SIZE(macaudio_snd_widgets);
+ card->dapm_routes = macaudio_dapm_routes;
+ card->num_dapm_routes = ARRAY_SIZE(macaudio_dapm_routes);
+ card->controls = macaudio_controls;
+ card->num_controls = ARRAY_SIZE(macaudio_controls);
+ card->probe = macaudio_probe;
+ card->late_probe = macaudio_late_probe;
+ card->component_chaining = true;
+ card->fully_routed = true;
+
+ if (of_id->data)
+ card->fixup_controls = of_id->data;
+ else
+ card->fixup_controls = macaudio_fallback_fixup_controls;
+
+ ret = macaudio_parse_of(data);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "failed OF parsing\n");
+
+ for_each_card_prelinks(card, i, link) {
+ if (link->no_pcm) {
+ link->ops = &macaudio_be_ops;
+ link->init = macaudio_be_init;
+ link->exit = macaudio_be_exit;
+ } else {
+ link->ops = &macaudio_fe_ops;
+ link->init = macaudio_fe_init;
+ }
+ }
+
+ return devm_snd_soc_register_card(dev, card);
+}
+
+static struct platform_driver macaudio_snd_driver = {
+ .probe = macaudio_snd_platform_probe,
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = macaudio_snd_device_id,
+ .pm = &snd_soc_pm_ops,
+ },
+};
+module_platform_driver(macaudio_snd_driver);
+
+MODULE_AUTHOR("Martin Povišer <povik+lin@cutebit.org>");
+MODULE_DESCRIPTION("Apple Silicon Macs machine-level sound driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c
index 3780aca..477819e 100644
--- a/sound/soc/apple/mca.c
+++ b/sound/soc/apple/mca.c
@@ -355,33 +355,6 @@ static int mca_be_prepare(struct snd_pcm_substream *substream,
return 0;
}
-static int mca_be_hw_free(struct snd_pcm_substream *substream,
- struct snd_soc_dai *dai)
-{
- struct mca_cluster *cl = mca_dai_to_cluster(dai);
- struct mca_data *mca = cl->host;
- struct mca_cluster *fe_cl;
-
- if (cl->port_driver < 0)
- return -EINVAL;
-
- /*
- * We are operating on a foreign cluster here, but since we
- * belong to the same PCM, accesses should have been
- * synchronized at ASoC level.
- */
- fe_cl = &mca->clusters[cl->port_driver];
- if (!mca_fe_clocks_in_use(fe_cl))
- return 0; /* Nothing to do */
-
- cl->clocks_in_use[substream->stream] = false;
-
- if (!mca_fe_clocks_in_use(fe_cl))
- mca_fe_disable_clocks(fe_cl);
-
- return 0;
-}
-
static unsigned int mca_crop_mask(unsigned int mask, int nchans)
{
while (hweight32(mask) > nchans)
@@ -464,6 +437,28 @@ static int mca_configure_serdes(struct mca_cluster *cl, int serdes_unit,
return -EINVAL;
}
+static int mca_fe_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct mca_cluster *cl = mca_dai_to_cluster(dai);
+ unsigned int mask, nchannels;
+
+ if (cl->tdm_slots) {
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ mask = cl->tdm_tx_mask;
+ else
+ mask = cl->tdm_rx_mask;
+
+ nchannels = hweight32(mask);
+ } else {
+ nchannels = 2;
+ }
+
+ return snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_CHANNELS,
+ 1, nchannels);
+}
+
static int mca_fe_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
unsigned int rx_mask, int slots, int slot_width)
{
@@ -680,6 +675,7 @@ static int mca_fe_hw_params(struct snd_pcm_substream *substream,
}
static const struct snd_soc_dai_ops mca_fe_ops = {
+ .startup = mca_fe_startup,
.set_fmt = mca_fe_set_fmt,
.set_bclk_ratio = mca_set_bclk_ratio,
.set_tdm_slot = mca_fe_set_tdm_slot,
@@ -756,6 +752,26 @@ static void mca_be_shutdown(struct snd_pcm_substream *substream,
struct mca_cluster *cl = mca_dai_to_cluster(dai);
struct mca_data *mca = cl->host;
+ if (cl->clocks_in_use[substream->stream] &&
+ !WARN_ON(cl->port_driver < 0)) {
+ struct mca_cluster *fe_cl = &mca->clusters[cl->port_driver];
+
+ /*
+ * Typically the CODECs we are paired with will require clocks
+ * to be present at time of mute with the 'mute_stream' op.
+ * We need to disable the clocks here at the earliest (hw_free
+ * would be too early).
+ *
+ * We are operating on a foreign cluster here, but since we
+ * belong to the same PCM, accesses should have been
+ * synchronized at ASoC level.
+ */
+ cl->clocks_in_use[substream->stream] = false;
+
+ if (!mca_fe_clocks_in_use(fe_cl))
+ mca_fe_disable_clocks(fe_cl);
+ }
+
cl->port_started[substream->stream] = false;
if (!mca_be_started(cl)) {
@@ -773,7 +789,6 @@ static void mca_be_shutdown(struct snd_pcm_substream *substream,
static const struct snd_soc_dai_ops mca_be_ops = {
.prepare = mca_be_prepare,
- .hw_free = mca_be_hw_free,
.startup = mca_be_startup,
.shutdown = mca_be_shutdown,
};
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 3429419..ba86072e 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -83,6 +83,7 @@
imply SND_SOC_CS42L52
imply SND_SOC_CS42L56
imply SND_SOC_CS42L73
+ imply SND_SOC_CS42L84
imply SND_SOC_CS4234
imply SND_SOC_CS4265
imply SND_SOC_CS4270
@@ -884,6 +885,10 @@
select REGMAP_I2C
select SND_SOC_CS42L42_CORE
+config SND_SOC_CS42L84
+ tristate "Cirrus Logic CS42L84 CODEC"
+ depends on I2C
+
config SND_SOC_CS4234
tristate "Cirrus Logic CS4234 CODEC"
depends on I2C
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 2078bb0..1d17b31 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -87,6 +87,7 @@
snd-soc-cs42l56-objs := cs42l56.o
snd-soc-cs42l73-objs := cs42l73.o
snd-soc-cs42l83-i2c-objs := cs42l83-i2c.o
+snd-soc-cs42l84-objs := cs42l84.o
snd-soc-cs4234-objs := cs4234.o
snd-soc-cs4265-objs := cs4265.o
snd-soc-cs4270-objs := cs4270.o
@@ -476,6 +477,7 @@
obj-$(CONFIG_SND_SOC_CS42L56) += snd-soc-cs42l56.o
obj-$(CONFIG_SND_SOC_CS42L73) += snd-soc-cs42l73.o
obj-$(CONFIG_SND_SOC_CS42L83) += snd-soc-cs42l83-i2c.o
+obj-$(CONFIG_SND_SOC_CS42L84) += snd-soc-cs42l84.o
obj-$(CONFIG_SND_SOC_CS4234) += snd-soc-cs4234.o
obj-$(CONFIG_SND_SOC_CS4265) += snd-soc-cs4265.o
obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o
diff --git a/sound/soc/codecs/cirrus,cs42l84.yaml b/sound/soc/codecs/cirrus,cs42l84.yaml
new file mode 100644
index 0000000..12bc6db
--- /dev/null
+++ b/sound/soc/codecs/cirrus,cs42l84.yaml
@@ -0,0 +1,60 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/cirrus,cs42l84.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Cirrus Logic CS42L84 audio CODEC
+
+maintainers:
+ - povik+lin@cutebit.org
+
+description:
+ The CS42L84 is a headphone jack codec made by Cirrus Logic and embedded
+ in personal computers sold by Apple. It was first seen in 2021 Macbook Pro
+ models.
+
+ It has stereo DAC for playback, mono ADC for capture, and is somewhat
+ similar to CS42L42 but with a different regmap.
+
+properties:
+ compatible:
+ enum:
+ - cirrus,cs42l84
+
+ reg:
+ description:
+ I2C address of the device
+ maxItems: 1
+
+ reset-gpios:
+ description:
+ Reset pin, asserted to reset the device, deasserted to bring
+ the device online
+ maxItems: 1
+
+ interrupts:
+ description:
+ Interrupt for the IRQ output line of the device
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+
+additionalProperties: false
+
+examples:
+ - |
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ jack_codec: codec@4b {
+ compatible = "cirrus,cs42l84";
+ reg = <0x4b>;
+ reset-gpios = <&pinctrl_nub 4 GPIO_ACTIVE_LOW>;
+ interrupts-extended = <&pinctrl_ap 180 IRQ_TYPE_LEVEL_LOW>;
+ #sound-dai-cells = <0>;
+ };
+ };
diff --git a/sound/soc/codecs/cs42l42.c b/sound/soc/codecs/cs42l42.c
index 94bcab8..14d7452 100644
--- a/sound/soc/codecs/cs42l42.c
+++ b/sound/soc/codecs/cs42l42.c
@@ -1150,7 +1150,6 @@ struct snd_soc_dai_driver cs42l42_dai = {
.formats = CS42L42_FORMATS,
},
.symmetric_rate = 1,
- .symmetric_sample_bits = 1,
.ops = &cs42l42_ops,
};
EXPORT_SYMBOL_NS_GPL(cs42l42_dai, SND_SOC_CS42L42_CORE);
@@ -1678,7 +1677,7 @@ irqreturn_t cs42l42_irq_thread(int irq, void *data)
return IRQ_NONE;
}
- /* Read sticky registers to clear interurpt */
+ /* Read sticky registers to clear interrupt */
for (i = 0; i < ARRAY_SIZE(stickies); i++) {
regmap_read(cs42l42->regmap, irq_params_table[i].status_addr,
&(stickies[i]));
@@ -2423,6 +2422,16 @@ int cs42l42_init(struct cs42l42_private *cs42l42)
(1 << CS42L42_ADC_PDN_SHIFT) |
(0 << CS42L42_PDN_ALL_SHIFT));
+ /*
+ * Configure a faster digital ramp time, to avoid an audible
+ * fade-in when streams start.
+ */
+ regmap_update_bits(cs42l42->regmap, CS42L42_SFTRAMP_RATE,
+ CS42L42_SFTRAMP_ASR_RATE_MASK |
+ CS42L42_SFTRAMP_DSR_RATE_MASK,
+ (10 << CS42L42_SFTRAMP_ASR_RATE_SHIFT) |
+ (1 << CS42L42_SFTRAMP_DSR_RATE_SHIFT));
+
ret = cs42l42_handle_device_data(cs42l42->dev, cs42l42);
if (ret != 0)
goto err_shutdown;
diff --git a/sound/soc/codecs/cs42l84.c b/sound/soc/codecs/cs42l84.c
new file mode 100644
index 0000000..54f6e75
--- /dev/null
+++ b/sound/soc/codecs/cs42l84.c
@@ -0,0 +1,1088 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * cs42l84.c -- CS42L84 ALSA SoC audio driver
+ *
+ * Copyright (C) The Asahi Linux Contributors
+ *
+ * Based on sound/soc/codecs/cs42l42{.c,.h}
+ * Copyright 2016 Cirrus Logic, Inc.
+ */
+
+#define DEBUG
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/gpio.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/acpi.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+#include <linux/gpio/consumer.h>
+#include <linux/of_device.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "cs42l84.h"
+#include "cirrus_legacy.h"
+
+struct cs42l84_private {
+ struct regmap *regmap;
+ struct device *dev;
+ struct gpio_desc *reset_gpio;
+ struct snd_soc_jack *jack;
+ struct mutex irq_lock;
+ u8 plug_state;
+ int pll_config;
+ int bclk;
+ u8 pll_mclk_f;
+ u32 srate;
+ u8 stream_use;
+ int hs_type;
+};
+
+/*
+static const struct reg_default cs42l84_reg_defaults[] = {
+};
+*/
+
+static bool cs42l84_volatile_register(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case CS42L84_DEVID ... CS42L84_DEVID+5:
+ case CS42L84_TSRS_PLUG_INT_STATUS:
+ case CS42L84_PLL_LOCK_STATUS:
+ case CS42L84_TSRS_PLUG_STATUS:
+ case CS42L84_HS_DET_STATUS2:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config cs42l84_regmap = {
+ .reg_bits = 16,
+ .val_bits = 8,
+
+ .volatile_reg = cs42l84_volatile_register,
+
+ .max_register = 0xffff,
+ /*
+ .reg_defaults = cs42l84_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(cs42l84_reg_defaults),
+ */
+ .cache_type = REGCACHE_RBTREE,
+
+ .use_single_read = true,
+ .use_single_write = true,
+};
+
+static int cs42l84_put_dac_vol(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *val)
+{
+ struct snd_soc_component *component = snd_soc_kcontrol_component(kctl);
+ struct soc_mixer_control *mc = (struct soc_mixer_control *) kctl->private_value;
+ int vola, volb;
+ int ret, ret2, updated = 0;
+
+ vola = val->value.integer.value[0] + mc->min;
+ volb = val->value.integer.value[1] + mc->min;
+
+ if (vola < mc->min || vola > mc->max || volb < mc->min || volb > mc->max)
+ return -EINVAL;
+
+ ret = snd_soc_component_update_bits(component, CS42L84_FRZ_CTL,
+ CS42L84_FRZ_CTL_ENGAGE,
+ CS42L84_FRZ_CTL_ENGAGE);
+ if (ret < 0)
+ goto bail;
+ updated |= ret;
+
+ ret = snd_soc_component_update_bits(component, CS42L84_DAC_CHA_VOL_LSB,
+ 0xff, vola & 0xff);
+ if (ret < 0)
+ goto bail;
+ updated |= ret;
+
+ ret = snd_soc_component_update_bits(component, CS42L84_DAC_CHA_VOL_MSB,
+ 0xff, (vola >> 8) & 0x01);
+ if (ret < 0)
+ goto bail;
+ updated |= ret;
+
+ ret = snd_soc_component_update_bits(component, CS42L84_DAC_CHB_VOL_LSB,
+ 0xff, volb & 0xff);
+ if (ret < 0)
+ goto bail;
+ updated |= ret;
+
+ ret = snd_soc_component_update_bits(component, CS42L84_DAC_CHB_VOL_MSB,
+ 0xff, (volb >> 8) & 0x01);
+ if (ret < 0)
+ goto bail;
+ ret |= updated;
+
+bail:
+ ret2 = snd_soc_component_update_bits(component, CS42L84_FRZ_CTL,
+ CS42L84_FRZ_CTL_ENGAGE, 0);
+ if (ret2 < 0 && ret >= 0)
+ ret = ret2;
+
+ return ret;
+}
+
+static int cs42l84_get_dac_vol(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *val)
+{
+ struct snd_soc_component *component = snd_soc_kcontrol_component(kctl);
+ struct soc_mixer_control *mc = (struct soc_mixer_control *) kctl->private_value;
+ int vola, volb;
+ int ret;
+
+ ret = snd_soc_component_read(component, CS42L84_DAC_CHA_VOL_LSB);
+ if (ret < 0)
+ return ret;
+ vola = ret;
+
+ ret = snd_soc_component_read(component, CS42L84_DAC_CHA_VOL_MSB);
+ if (ret < 0)
+ return ret;
+ vola |= (ret & 1) << 8;
+
+ ret = snd_soc_component_read(component, CS42L84_DAC_CHB_VOL_LSB);
+ if (ret < 0)
+ return ret;
+ volb = ret;
+
+ ret = snd_soc_component_read(component, CS42L84_DAC_CHB_VOL_MSB);
+ if (ret < 0)
+ return ret;
+ volb |= (ret & 1) << 8;
+
+ if (vola & BIT(8))
+ vola |= ~((int)(BIT(8) - 1));
+ if (volb & BIT(8))
+ volb |= ~((int)(BIT(8) - 1));
+
+ val->value.integer.value[0] = vola - mc->min;
+ val->value.integer.value[1] = volb - mc->min;
+
+ return 0;
+}
+
+/* TODO */
+static const DECLARE_TLV_DB_SCALE(cs42l84_dac_tlv, -12800, 50, true);
+
+static const struct snd_kcontrol_new cs42l84_snd_controls[] = {
+ SOC_DOUBLE_R_S_EXT_TLV("DAC Playback Volume", CS42L84_DAC_CHA_VOL_LSB,
+ CS42L84_DAC_CHB_VOL_LSB, 0, -256, 24, 8, 0,
+ cs42l84_get_dac_vol, cs42l84_put_dac_vol, cs42l84_dac_tlv),
+ SOC_SINGLE("ADC Preamp Gain", CS42L84_ADC_CTL1,
+ CS42L84_ADC_CTL1_PREAMP_GAIN_SHIFT, 2, 0),
+ SOC_SINGLE("ADC PGA Gain", CS42L84_ADC_CTL1,
+ CS42L84_ADC_CTL1_PGA_GAIN_SHIFT, 31, 0),
+ SOC_SINGLE("ADC WNF Switch", CS42L84_ADC_CTL4,
+ CS42L84_ADC_CTL4_WNF_EN_SHIFT, 1, 0),
+ SOC_SINGLE("WNF Corner Frequency", CS42L84_ADC_CTL4,
+ CS42L84_ADC_CTL4_WNF_CF_SHIFT, 3, 0),
+ SOC_SINGLE("ADC HPF Switch", CS42L84_ADC_CTL4,
+ CS42L84_ADC_CTL4_HPF_EN_SHIFT, 1, 0),
+ SOC_SINGLE("HPF Corner Frequency", CS42L84_ADC_CTL4,
+ CS42L84_ADC_CTL4_HPF_CF_SHIFT, 3, 0),
+};
+
+static const char* const cs42l84_mux_text[] = {
+ "Blank", "ADC", "ASP RX CH1", "ASP RX CH2",
+};
+
+static const unsigned int cs42l84_mux_values[] = {
+ 0b0000, 0b0111, 0b1101, 0b1110,
+};
+
+static SOC_VALUE_ENUM_SINGLE_DECL(cs42l84_daca_mux_enum,
+ CS42L84_BUS_DAC_SRC, CS42L84_BUS_DAC_SRC_DACA_SHIFT,
+ 0b1111, cs42l84_mux_text, cs42l84_mux_values);
+
+static SOC_VALUE_ENUM_SINGLE_DECL(cs42l84_dacb_mux_enum,
+ CS42L84_BUS_DAC_SRC, CS42L84_BUS_DAC_SRC_DACB_SHIFT,
+ 0b1111, cs42l84_mux_text, cs42l84_mux_values);
+
+static SOC_VALUE_ENUM_SINGLE_DECL(cs42l84_sdout1_mux_enum,
+ CS42L84_BUS_ASP_TX_SRC, CS42L84_BUS_ASP_TX_SRC_CH1_SHIFT,
+ 0b1111, cs42l84_mux_text, cs42l84_mux_values);
+
+static const struct snd_kcontrol_new cs42l84_daca_mux_ctrl =
+ SOC_DAPM_ENUM("DACA Select", cs42l84_daca_mux_enum);
+
+static const struct snd_kcontrol_new cs42l84_dacb_mux_ctrl =
+ SOC_DAPM_ENUM("DACB Select", cs42l84_dacb_mux_enum);
+
+static const struct snd_kcontrol_new cs42l84_sdout1_mux_ctrl =
+ SOC_DAPM_ENUM("SDOUT1 Select", cs42l84_sdout1_mux_enum);
+
+static const struct snd_soc_dapm_widget cs42l84_dapm_widgets[] = {
+ /* Playback Path */
+ SND_SOC_DAPM_OUTPUT("HP"),
+ SND_SOC_DAPM_DAC("DAC", NULL, CS42L84_MSM_BLOCK_EN2, CS42L84_MSM_BLOCK_EN2_DAC_SHIFT, 0),
+ SND_SOC_DAPM_MUX("DACA Select", SND_SOC_NOPM, 0, 0, &cs42l84_daca_mux_ctrl),
+ SND_SOC_DAPM_MUX("DACB Select", SND_SOC_NOPM, 0, 0, &cs42l84_dacb_mux_ctrl),
+ SND_SOC_DAPM_AIF_IN("SDIN1", NULL, 0, CS42L84_ASP_RX_EN, CS42L84_ASP_RX_EN_CH1_SHIFT, 0),
+ SND_SOC_DAPM_AIF_IN("SDIN2", NULL, 1, CS42L84_ASP_RX_EN, CS42L84_ASP_RX_EN_CH2_SHIFT, 0),
+
+ /* Capture Path */
+ SND_SOC_DAPM_INPUT("HS"),
+ SND_SOC_DAPM_ADC("ADC", NULL, CS42L84_MSM_BLOCK_EN2, CS42L84_MSM_BLOCK_EN2_ADC_SHIFT, 0),
+ SND_SOC_DAPM_MUX("SDOUT1 Select", SND_SOC_NOPM, 0, 0, &cs42l84_sdout1_mux_ctrl),
+ SND_SOC_DAPM_AIF_OUT("SDOUT1", NULL, 0, CS42L84_ASP_TX_EN, CS42L84_ASP_TX_EN_CH1_SHIFT, 0),
+
+ /* Playback/Capture Requirements */
+ SND_SOC_DAPM_SUPPLY("BUS", CS42L84_MSM_BLOCK_EN2, CS42L84_MSM_BLOCK_EN2_BUS_SHIFT, 0, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("ASP", CS42L84_MSM_BLOCK_EN2, CS42L84_MSM_BLOCK_EN2_ASP_SHIFT, 0, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("BCLK", CS42L84_ASP_CTL, CS42L84_ASP_CTL_BCLK_EN_SHIFT, 0, NULL, 0),
+};
+
+static const struct snd_soc_dapm_route cs42l84_audio_map[] = {
+ /* Playback Path */
+ {"HP", NULL, "DAC"},
+ {"DAC", NULL, "DACA Select"},
+ {"DAC", NULL, "DACB Select"},
+ {"DACA Select", "ASP RX CH1", "SDIN1"},
+ {"DACA Select", "ASP RX CH2", "SDIN2"},
+ {"DACB Select", "ASP RX CH1", "SDIN1"},
+ {"DACB Select", "ASP RX CH2", "SDIN2"},
+ {"SDIN1", NULL, "Playback"},
+ {"SDIN2", NULL, "Playback"},
+
+ {"ADC", NULL, "HS"},
+ {"SDOUT1 Select", "ADC", "ADC"},
+ {"SDOUT1", NULL, "SDOUT1 Select"},
+ {"Capture", NULL, "SDOUT1"},
+
+ /* Playback Requirements */
+ {"DAC", NULL, "BUS"},
+ {"SDIN1", NULL, "ASP"},
+ {"SDIN2", NULL, "ASP"},
+ {"SDIN1", NULL, "BCLK"},
+ {"SDIN2", NULL, "BCLK"},
+
+ /* Capture Requirements */
+ {"SDOUT1", NULL, "BUS"},
+ {"SDOUT1", NULL, "ASP"},
+ {"SDOUT1", NULL, "BCLK"},
+};
+
+static int cs42l84_set_jack(struct snd_soc_component *component, struct snd_soc_jack *jk, void *d)
+{
+ struct cs42l84_private *cs42l84 = snd_soc_component_get_drvdata(component);
+
+ /* Prevent race with interrupt handler */
+ mutex_lock(&cs42l84->irq_lock);
+ cs42l84->jack = jk;
+ snd_soc_jack_report(jk, cs42l84->hs_type, SND_JACK_HEADSET);
+ mutex_unlock(&cs42l84->irq_lock);
+
+ return 0;
+}
+
+static int cs42l84_component_probe(struct snd_soc_component *component)
+{
+ snd_soc_component_update_bits(component, CS42L84_ASP_CTL,
+ CS42L84_ASP_CTL_TDM_MODE, 0);
+ snd_soc_component_update_bits(component, CS42L84_HP_VOL_CTL,
+ CS42L84_HP_VOL_CTL_SOFT | CS42L84_HP_VOL_CTL_ZERO_CROSS,
+ CS42L84_HP_VOL_CTL_ZERO_CROSS);
+
+ /* TDM settings */
+ snd_soc_component_update_bits(component, CS42L84_ASP_RX_CH1_CTL1,
+ CS42L84_ASP_RX_CHx_CTL1_EDGE |
+ CS42L84_ASP_RX_CHx_CTL1_SLOT_START_LSB, 0);
+ snd_soc_component_update_bits(component, CS42L84_ASP_RX_CH1_CTL2,
+ CS42L84_ASP_RX_CHx_CTL2_SLOT_START_MSB, 0);
+ snd_soc_component_update_bits(component, CS42L84_ASP_RX_CH2_CTL1,
+ CS42L84_ASP_RX_CHx_CTL1_EDGE |
+ CS42L84_ASP_RX_CHx_CTL1_SLOT_START_LSB,
+ CS42L84_ASP_RX_CHx_CTL1_EDGE);
+ snd_soc_component_update_bits(component, CS42L84_ASP_RX_CH2_CTL2,
+ CS42L84_ASP_RX_CHx_CTL2_SLOT_START_MSB, 0);
+ snd_soc_component_update_bits(component, CS42L84_ASP_TX_CH1_CTL1,
+ CS42L84_ASP_RX_CHx_CTL1_EDGE | \
+ CS42L84_ASP_RX_CHx_CTL1_SLOT_START_LSB, 0);
+ snd_soc_component_update_bits(component, CS42L84_ASP_TX_CH1_CTL2,
+ CS42L84_ASP_RX_CHx_CTL2_SLOT_START_MSB, 0);
+ snd_soc_component_update_bits(component, CS42L84_ASP_TX_CH2_CTL1,
+ CS42L84_ASP_RX_CHx_CTL1_EDGE | \
+ CS42L84_ASP_RX_CHx_CTL1_SLOT_START_LSB,
+ CS42L84_ASP_RX_CHx_CTL1_EDGE);
+ snd_soc_component_update_bits(component, CS42L84_ASP_TX_CH2_CTL2,
+ CS42L84_ASP_RX_CHx_CTL2_SLOT_START_MSB, 0);
+ /* Routing defaults */
+ snd_soc_component_write(component, CS42L84_BUS_DAC_SRC,
+ 0b1101 << CS42L84_BUS_DAC_SRC_DACA_SHIFT |
+ 0b1110 << CS42L84_BUS_DAC_SRC_DACB_SHIFT);
+ snd_soc_component_write(component, CS42L84_BUS_ASP_TX_SRC,
+ 0b0111 << CS42L84_BUS_ASP_TX_SRC_CH1_SHIFT);
+
+ return 0;
+}
+
+static const struct snd_soc_component_driver soc_component_dev_cs42l84 = {
+ .set_jack = cs42l84_set_jack,
+ .probe = cs42l84_component_probe,
+ .controls = cs42l84_snd_controls,
+ .num_controls = ARRAY_SIZE(cs42l84_snd_controls),
+ .dapm_widgets = cs42l84_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(cs42l84_dapm_widgets),
+ .dapm_routes = cs42l84_audio_map,
+ .num_dapm_routes = ARRAY_SIZE(cs42l84_audio_map),
+ .endianness = 1,
+};
+
+struct cs42l84_pll_params {
+ u32 bclk;
+ u8 mclk_src_sel;
+ u8 bclk_prediv;
+ u8 pll_div_int;
+ u32 pll_div_frac;
+ u8 pll_mode;
+ u8 pll_divout;
+ u32 mclk_int;
+};
+
+/*
+ * Common PLL Settings for given BCLK
+ */
+static const struct cs42l84_pll_params pll_ratio_table[] = {
+ { 3072000, 1, 0, 0x40, 0x000000, 0x03, 0x10, 12288000},
+ { 6144000, 1, 1, 0x40, 0x000000, 0x03, 0x10, 12288000},
+ { 12288000, 0, 0, 0, 0, 0, 0, 12288000},
+ { 24576000, 1, 3, 0x40, 0x000000, 0x03, 0x10, 12288000},
+};
+
+static int cs42l84_pll_config(struct snd_soc_component *component)
+{
+ struct cs42l84_private *cs42l84 = snd_soc_component_get_drvdata(component);
+ int i;
+ u32 clk;
+ u32 fsync;
+
+ clk = cs42l84->bclk;
+
+ /* Don't reconfigure if there is an audio stream running */
+ if (cs42l84->stream_use) {
+ if (pll_ratio_table[cs42l84->pll_config].bclk == clk)
+ return 0;
+ else
+ return -EBUSY;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(pll_ratio_table); i++) {
+ if (pll_ratio_table[i].bclk == clk) {
+ cs42l84->pll_config = i;
+ break;
+ }
+ }
+
+ if (i == ARRAY_SIZE(pll_ratio_table))
+ return -EINVAL;
+
+ /* Set up the LRCLK */
+ fsync = clk / cs42l84->srate;
+ if (((fsync * cs42l84->srate) != clk)
+ || ((fsync % 2) != 0)) {
+ dev_err(component->dev,
+ "Unsupported bclk %d/sample rate %d\n",
+ clk, cs42l84->srate);
+ return -EINVAL;
+ }
+
+ /* Set the LRCLK period */
+ snd_soc_component_update_bits(component, CS42L84_ASP_FSYNC_CTL2,
+ CS42L84_ASP_FSYNC_CTL2_BCLK_PERIOD_LO,
+ FIELD_PREP(CS42L84_ASP_FSYNC_CTL2_BCLK_PERIOD_LO, fsync & 0x7f));
+ snd_soc_component_update_bits(component, CS42L84_ASP_FSYNC_CTL3,
+ CS42L84_ASP_FSYNC_CTL3_BCLK_PERIOD_HI,
+ FIELD_PREP(CS42L84_ASP_FSYNC_CTL3_BCLK_PERIOD_HI, fsync >> 7));
+
+ /* Save what the MCLK will be */
+ switch (pll_ratio_table[i].mclk_int) {
+ case 12000000:
+ cs42l84->pll_mclk_f = CS42L84_CCM_CTL1_MCLK_F_12MHZ;
+ break;
+ case 12288000:
+ cs42l84->pll_mclk_f = CS42L84_CCM_CTL1_MCLK_F_12_288KHZ;
+ break;
+ case 24000000:
+ cs42l84->pll_mclk_f = CS42L84_CCM_CTL1_MCLK_F_24MHZ;
+ break;
+ case 24576000:
+ cs42l84->pll_mclk_f = CS42L84_CCM_CTL1_MCLK_F_24_576KHZ;
+ break;
+ }
+
+ snd_soc_component_update_bits(component, CS42L84_PLL_CTL1, CS42L84_PLL_CTL1_EN, 0);
+
+ if (pll_ratio_table[i].mclk_src_sel) {
+ /* Configure PLL */
+ snd_soc_component_update_bits(component,
+ CS42L84_CCM_CTL3, CS42L84_CCM_CTL3_REFCLK_DIV,
+ FIELD_PREP(CS42L84_CCM_CTL3_REFCLK_DIV, pll_ratio_table[i].bclk_prediv));
+ snd_soc_component_write(component,
+ CS42L84_PLL_DIV_INT,
+ pll_ratio_table[i].pll_div_int);
+ snd_soc_component_write(component,
+ CS42L84_PLL_DIV_FRAC0,
+ pll_ratio_table[i].pll_div_frac);
+ snd_soc_component_write(component,
+ CS42L84_PLL_DIV_FRAC1,
+ pll_ratio_table[i].pll_div_frac >> 8);
+ snd_soc_component_write(component,
+ CS42L84_PLL_DIV_FRAC2,
+ pll_ratio_table[i].pll_div_frac >> 16);
+ snd_soc_component_update_bits(component,
+ CS42L84_PLL_CTL1, CS42L84_PLL_CTL1_MODE,
+ FIELD_PREP(CS42L84_PLL_CTL1_MODE, pll_ratio_table[i].pll_mode));
+ snd_soc_component_write(component,
+ CS42L84_PLL_DIVOUT,
+ pll_ratio_table[i].pll_divout);
+ }
+
+ return 0;
+}
+
+static int cs42l84_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+ switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+ case SND_SOC_DAIFMT_BC_FC:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Bitclock/frame inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_IB_IF:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int cs42l84_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *component = dai->component;
+ struct cs42l84_private *cs42l84 = snd_soc_component_get_drvdata(component);
+ int ret;
+ u32 ccm_samp_rate;
+
+ cs42l84->srate = params_rate(params);
+
+ ret = cs42l84_pll_config(component);
+ if (ret)
+ return ret;
+
+ switch (params_rate(params)) {
+ case 44100:
+ ccm_samp_rate = CS42L84_CCM_SAMP_RATE_RATE_44K1HZ;
+ break;
+ case 48000:
+ ccm_samp_rate = CS42L84_CCM_SAMP_RATE_RATE_48KHZ;
+ break;
+ case 88200:
+ ccm_samp_rate = CS42L84_CCM_SAMP_RATE_RATE_88K2HZ;
+ break;
+ case 96000:
+ ccm_samp_rate = CS42L84_CCM_SAMP_RATE_RATE_96KHZ;
+ break;
+ case 176400:
+ ccm_samp_rate = CS42L84_CCM_SAMP_RATE_RATE_176K4HZ;
+ break;
+ case 192000:
+ ccm_samp_rate = CS42L84_CCM_SAMP_RATE_RATE_192KHZ;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_component_write(component, CS42L84_CCM_SAMP_RATE, ccm_samp_rate);
+
+ switch (substream->stream) {
+ case SNDRV_PCM_STREAM_PLAYBACK:
+ snd_soc_component_write(component, CS42L84_ASP_RX_CH1_WIDTH,
+ params_width(params) - 1);
+ snd_soc_component_write(component, CS42L84_ASP_RX_CH2_WIDTH,
+ params_width(params) - 1);
+ break;
+
+ case SNDRV_PCM_STREAM_CAPTURE:
+ snd_soc_component_write(component, CS42L84_ASP_TX_CH1_WIDTH,
+ params_width(params) - 1);
+ snd_soc_component_write(component, CS42L84_ASP_TX_CH2_WIDTH,
+ params_width(params) - 1);
+ break;
+ }
+
+ return 0;
+}
+
+static int cs42l84_set_sysclk(struct snd_soc_dai *dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_component *component = dai->component;
+ struct cs42l84_private *cs42l84 = snd_soc_component_get_drvdata(component);
+ int i;
+
+ if (freq == 0) {
+ cs42l84->bclk = 0;
+ return 0;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(pll_ratio_table); i++) {
+ if (pll_ratio_table[i].bclk == freq) {
+ cs42l84->bclk = freq;
+ return 0;
+ }
+ }
+
+ dev_err(component->dev, "BCLK %u not supported\n", freq);
+
+ return -EINVAL;
+}
+
+static int cs42l84_mute_stream(struct snd_soc_dai *dai, int mute, int stream)
+{
+ struct snd_soc_component *component = dai->component;
+ struct cs42l84_private *cs42l84 = snd_soc_component_get_drvdata(component);
+ unsigned int regval;
+ int ret;
+
+ if (mute) {
+ /* Mute the headphone */
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+ snd_soc_component_update_bits(component, CS42L84_DAC_CTL1,
+ CS42L84_DAC_CTL1_UNMUTE, 0);
+ cs42l84->stream_use &= ~(1 << stream);
+ if (!cs42l84->stream_use) {
+ /* Must disconnect PLL before stopping it */
+ snd_soc_component_write(component, CS42L84_CCM_CTL1,
+ CS42L84_CCM_CTL1_RCO);
+
+ usleep_range(150, 300);
+
+ snd_soc_component_update_bits(component, CS42L84_PLL_CTL1,
+ CS42L84_PLL_CTL1_EN, 0);
+
+ snd_soc_component_update_bits(component, CS42L84_CCM_CTL4,
+ CS42L84_CCM_CTL4_REFCLK_EN, 0);
+ }
+ } else {
+ if (!cs42l84->stream_use) {
+ /* SCLK must be running before codec unmute.
+ *
+ * Note carried over from CS42L42:
+ *
+ * PLL must not be started with ADC and HP both off
+ * otherwise the FILT+ supply will not charge properly.
+ * DAPM widgets power-up before stream unmute so at least
+ * one of the "DAC" or "ADC" widgets will already have
+ * powered-up.
+ */
+
+ snd_soc_component_update_bits(component, CS42L84_CCM_CTL4,
+ CS42L84_CCM_CTL4_REFCLK_EN,
+ CS42L84_CCM_CTL4_REFCLK_EN);
+
+ if (pll_ratio_table[cs42l84->pll_config].mclk_src_sel) {
+ snd_soc_component_update_bits(component, CS42L84_PLL_CTL1,
+ CS42L84_PLL_CTL1_EN,
+ CS42L84_PLL_CTL1_EN);
+ /* TODO: should we be doing something with divout here? */
+
+ ret = regmap_read_poll_timeout(cs42l84->regmap,
+ CS42L84_PLL_LOCK_STATUS,
+ regval,
+ (regval & CS42L84_PLL_LOCK_STATUS_LOCKED),
+ CS42L84_PLL_LOCK_POLL_US,
+ CS42L84_PLL_LOCK_TIMEOUT_US);
+ if (ret < 0)
+ dev_warn(component->dev, "PLL failed to lock: %d\n", ret);
+
+ if (regval & CS42L84_PLL_LOCK_STATUS_ERROR)
+ dev_warn(component->dev, "PLL lock error\n");
+
+ /* PLL must be running to drive glitchless switch logic */
+ snd_soc_component_update_bits(component,
+ CS42L84_CCM_CTL1,
+ CS42L84_CCM_CTL1_MCLK_SRC | CS42L84_CCM_CTL1_MCLK_FREQ,
+ FIELD_PREP(CS42L84_CCM_CTL1_MCLK_SRC, CS42L84_CCM_CTL1_MCLK_SRC_PLL)
+ | FIELD_PREP(CS42L84_CCM_CTL1_MCLK_FREQ, cs42l84->pll_mclk_f));
+ usleep_range(CS42L84_CLOCK_SWITCH_DELAY_US, CS42L84_CLOCK_SWITCH_DELAY_US*2);
+ } else {
+ snd_soc_component_update_bits(component,
+ CS42L84_CCM_CTL1,
+ CS42L84_CCM_CTL1_MCLK_SRC | CS42L84_CCM_CTL1_MCLK_FREQ,
+ FIELD_PREP(CS42L84_CCM_CTL1_MCLK_SRC, CS42L84_CCM_CTL1_MCLK_SRC_BCLK)
+ | FIELD_PREP(CS42L84_CCM_CTL1_MCLK_FREQ, cs42l84->pll_mclk_f));
+ usleep_range(CS42L84_CLOCK_SWITCH_DELAY_US, CS42L84_CLOCK_SWITCH_DELAY_US*2);
+ }
+ }
+ cs42l84->stream_use |= 1 << stream;
+
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+ /* Un-mute the headphone */
+ snd_soc_component_update_bits(component, CS42L84_DAC_CTL1,
+ CS42L84_DAC_CTL1_UNMUTE,
+ CS42L84_DAC_CTL1_UNMUTE);
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops cs42l84_ops = {
+ .hw_params = cs42l84_pcm_hw_params,
+ .set_fmt = cs42l84_set_dai_fmt,
+ .set_sysclk = cs42l84_set_sysclk,
+ .mute_stream = cs42l84_mute_stream,
+};
+
+#define CS42L84_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\
+ SNDRV_PCM_FMTBIT_S24_LE |\
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_driver cs42l84_dai = {
+ .name = "cs42l84",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000,
+ .formats = CS42L84_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000,
+ .formats = CS42L84_FORMATS,
+ },
+ .symmetric_rate = 1,
+ .symmetric_sample_bits = 1,
+ .ops = &cs42l84_ops,
+};
+
+struct cs42l84_irq_params {
+ u16 status_addr;
+ u16 mask_addr;
+ u8 mask;
+};
+
+static const struct cs42l84_irq_params irq_params_table[] = {
+ {CS42L84_TSRS_PLUG_INT_STATUS, CS42L84_TSRS_PLUG_INT_MASK,
+ CS42L84_TSRS_PLUG_VAL_MASK}
+};
+
+static void cs42l84_detect_hs(struct cs42l84_private *cs42l84)
+{
+ unsigned int reg;
+
+ /* Power up HSBIAS */
+ regmap_update_bits(cs42l84->regmap,
+ CS42L84_MISC_DET_CTL,
+ CS42L84_MISC_DET_CTL_HSBIAS_CTL | CS42L84_MISC_DET_CTL_DETECT_MODE,
+ FIELD_PREP(CS42L84_MISC_DET_CTL_HSBIAS_CTL, 3) | /* 2.7 V */
+ FIELD_PREP(CS42L84_MISC_DET_CTL_DETECT_MODE, 0));
+
+ /* Power up level detection circuitry */
+ regmap_update_bits(cs42l84->regmap,
+ CS42L84_MISC_DET_CTL,
+ CS42L84_MISC_DET_CTL_PDN_MIC_LVL_DET, 0);
+
+ /* TODO: Optimize */
+ msleep(100);
+
+ /* Connect HSBIAS in CTIA wiring */
+ /* TODO: Should likely be subject of detection */
+ regmap_write(cs42l84->regmap,
+ CS42L84_HS_SWITCH_CTL,
+ CS42L84_HS_SWITCH_CTL_REF_HS3 | \
+ CS42L84_HS_SWITCH_CTL_HSB_FILT_HS3 | \
+ CS42L84_HS_SWITCH_CTL_GNDHS_HS3 | \
+ CS42L84_HS_SWITCH_CTL_HSB_HS4);
+ regmap_update_bits(cs42l84->regmap,
+ CS42L84_HS_DET_CTL2,
+ CS42L84_HS_DET_CTL2_SET,
+ FIELD_PREP(CS42L84_HS_DET_CTL2_SET, 0));
+
+ regmap_update_bits(cs42l84->regmap,
+ CS42L84_MISC_DET_CTL,
+ CS42L84_MISC_DET_CTL_DETECT_MODE,
+ FIELD_PREP(CS42L84_MISC_DET_CTL_DETECT_MODE, 3));
+
+ /* TODO: Optimize */
+ msleep(100);
+
+ regmap_read(cs42l84->regmap, CS42L84_HS_DET_STATUS2, ®);
+ regmap_update_bits(cs42l84->regmap,
+ CS42L84_MISC_DET_CTL,
+ CS42L84_MISC_DET_CTL_PDN_MIC_LVL_DET,
+ CS42L84_MISC_DET_CTL_PDN_MIC_LVL_DET);
+
+ switch (reg & 0b11) {
+ case 0b11: /* shorted */
+ case 0b00: /* open */
+ /* Power down HSBIAS */
+ regmap_update_bits(cs42l84->regmap,
+ CS42L84_MISC_DET_CTL,
+ CS42L84_MISC_DET_CTL_HSBIAS_CTL,
+ FIELD_PREP(CS42L84_MISC_DET_CTL_HSBIAS_CTL, 1)); /* 0.0 V */
+ break;
+ }
+
+ switch (reg & 0b11) {
+ case 0b10: /* load */
+ dev_dbg(cs42l84->dev, "Detected mic\n");
+ cs42l84->hs_type = SND_JACK_HEADSET;
+ snd_soc_jack_report(cs42l84->jack, SND_JACK_HEADSET,
+ SND_JACK_HEADSET);
+ break;
+
+ case 0b00: /* open */
+ dev_dbg(cs42l84->dev, "Detected open circuit on HS4\n");
+ fallthrough;
+ case 0b11: /* shorted */
+ default:
+ snd_soc_jack_report(cs42l84->jack, SND_JACK_HEADPHONE,
+ SND_JACK_HEADSET);
+ cs42l84->hs_type = SND_JACK_HEADPHONE;
+ dev_dbg(cs42l84->dev, "Detected bare headphone (no mic)\n");
+ }
+}
+
+static void cs42l84_revert_hs(struct cs42l84_private *cs42l84)
+{
+ /* Power down HSBIAS */
+ regmap_update_bits(cs42l84->regmap,
+ CS42L84_MISC_DET_CTL,
+ CS42L84_MISC_DET_CTL_HSBIAS_CTL | CS42L84_MISC_DET_CTL_DETECT_MODE,
+ FIELD_PREP(CS42L84_MISC_DET_CTL_HSBIAS_CTL, 1) | /* 0.0 V */
+ FIELD_PREP(CS42L84_MISC_DET_CTL_DETECT_MODE, 0));
+
+ /* Disconnect HSBIAS */
+ regmap_write(cs42l84->regmap,
+ CS42L84_HS_SWITCH_CTL,
+ CS42L84_HS_SWITCH_CTL_REF_HS3 | \
+ CS42L84_HS_SWITCH_CTL_REF_HS4 | \
+ CS42L84_HS_SWITCH_CTL_HSB_FILT_HS3 | \
+ CS42L84_HS_SWITCH_CTL_HSB_FILT_HS4 | \
+ CS42L84_HS_SWITCH_CTL_GNDHS_HS3 | \
+ CS42L84_HS_SWITCH_CTL_GNDHS_HS4);
+ regmap_update_bits(cs42l84->regmap,
+ CS42L84_HS_DET_CTL2,
+ CS42L84_HS_DET_CTL2_SET,
+ FIELD_PREP(CS42L84_HS_DET_CTL2_SET, 2));
+}
+
+static irqreturn_t cs42l84_irq_thread(int irq, void *data)
+{
+ struct cs42l84_private *cs42l84 = (struct cs42l84_private *)data;
+ unsigned int stickies[1];
+ unsigned int masks[1];
+ unsigned int reg;
+ u8 current_plug_status;
+ int i;
+
+ mutex_lock(&cs42l84->irq_lock);
+ /* Read sticky registers to clear interurpt */
+ for (i = 0; i < ARRAY_SIZE(stickies); i++) {
+ regmap_read(cs42l84->regmap, irq_params_table[i].status_addr,
+ &(stickies[i]));
+ regmap_read(cs42l84->regmap, irq_params_table[i].mask_addr,
+ &(masks[i]));
+ stickies[i] = stickies[i] & (~masks[i]) &
+ irq_params_table[i].mask;
+ }
+
+ if ((~masks[0]) & irq_params_table[0].mask) {
+ regmap_read(cs42l84->regmap, CS42L84_TSRS_PLUG_STATUS, ®);
+ current_plug_status = (((char) reg) &
+ (CS42L84_TS_PLUG | CS42L84_TS_UNPLUG)) >>
+ CS42L84_TS_PLUG_SHIFT;
+
+ switch (current_plug_status) {
+ case CS42L84_PLUG:
+ if (cs42l84->plug_state != CS42L84_PLUG) {
+ cs42l84->plug_state = CS42L84_PLUG;
+ dev_dbg(cs42l84->dev, "Plug event\n");
+
+ cs42l84_detect_hs(cs42l84);
+
+ /*
+ * Check the tip sense status again, and possibly invalidate
+ * the detection result
+ *
+ * Thanks to debounce, this should reliably indicate if the tip
+ * was disconnected at any point during the detection procedure.
+ */
+ regmap_read(cs42l84->regmap, CS42L84_TSRS_PLUG_STATUS, ®);
+ current_plug_status = (((char) reg) &
+ (CS42L84_TS_PLUG | CS42L84_TS_UNPLUG)) >>
+ CS42L84_TS_PLUG_SHIFT;
+ if (current_plug_status != CS42L84_PLUG) {
+ dev_dbg(cs42l84->dev, "Wobbly connection, detection invalidated\n");
+ cs42l84->plug_state = CS42L84_UNPLUG;
+ cs42l84_revert_hs(cs42l84);
+ }
+ }
+ break;
+
+ case CS42L84_UNPLUG:
+ if (cs42l84->plug_state != CS42L84_UNPLUG) {
+ cs42l84->plug_state = CS42L84_UNPLUG;
+ dev_dbg(cs42l84->dev, "Unplug event\n");
+
+ cs42l84_revert_hs(cs42l84);
+ cs42l84->hs_type = 0;
+ snd_soc_jack_report(cs42l84->jack, 0,
+ SND_JACK_HEADSET);
+ }
+ break;
+
+ default:
+ if (cs42l84->plug_state != CS42L84_TRANS)
+ cs42l84->plug_state = CS42L84_TRANS;
+ }
+ }
+ mutex_unlock(&cs42l84->irq_lock);
+
+ return IRQ_HANDLED;
+}
+
+static void cs42l84_set_interrupt_masks(struct cs42l84_private *cs42l84)
+{
+ regmap_update_bits(cs42l84->regmap, CS42L84_TSRS_PLUG_INT_MASK,
+ CS42L84_RS_PLUG | CS42L84_RS_UNPLUG |
+ CS42L84_TS_PLUG | CS42L84_TS_UNPLUG,
+ CS42L84_RS_PLUG | CS42L84_RS_UNPLUG);
+}
+
+static void cs42l84_setup_plug_detect(struct cs42l84_private *cs42l84)
+{
+ unsigned int reg;
+
+ /* Set up plug detection */
+ regmap_update_bits(cs42l84->regmap, CS42L84_MIC_DET_CTL4,
+ CS42L84_MIC_DET_CTL4_LATCH_TO_VP,
+ CS42L84_MIC_DET_CTL4_LATCH_TO_VP);
+ regmap_update_bits(cs42l84->regmap, CS42L84_TIP_SENSE_CTL2,
+ CS42L84_TIP_SENSE_CTL2_MODE,
+ FIELD_PREP(CS42L84_TIP_SENSE_CTL2_MODE, CS42L84_TIP_SENSE_CTL2_MODE_SHORT_DET));
+ regmap_update_bits(cs42l84->regmap, CS42L84_RING_SENSE_CTL,
+ CS42L84_RING_SENSE_CTL_INV | CS42L84_RING_SENSE_CTL_UNK1 |
+ CS42L84_RING_SENSE_CTL_RISETIME | CS42L84_RING_SENSE_CTL_FALLTIME,
+ CS42L84_RING_SENSE_CTL_INV | CS42L84_RING_SENSE_CTL_UNK1 |
+ FIELD_PREP(CS42L84_RING_SENSE_CTL_RISETIME, CS42L84_DEBOUNCE_TIME_125MS) |
+ FIELD_PREP(CS42L84_RING_SENSE_CTL_FALLTIME, CS42L84_DEBOUNCE_TIME_125MS));
+ regmap_update_bits(cs42l84->regmap, CS42L84_TIP_SENSE_CTL,
+ CS42L84_TIP_SENSE_CTL_INV |
+ CS42L84_TIP_SENSE_CTL_RISETIME | CS42L84_TIP_SENSE_CTL_FALLTIME,
+ CS42L84_TIP_SENSE_CTL_INV |
+ FIELD_PREP(CS42L84_TIP_SENSE_CTL_RISETIME, CS42L84_DEBOUNCE_TIME_500MS) |
+ FIELD_PREP(CS42L84_TIP_SENSE_CTL_FALLTIME, CS42L84_DEBOUNCE_TIME_125MS));
+ regmap_update_bits(cs42l84->regmap, CS42L84_MSM_BLOCK_EN3,
+ CS42L84_MSM_BLOCK_EN3_TR_SENSE,
+ CS42L84_MSM_BLOCK_EN3_TR_SENSE);
+
+ /* Save the initial status of the tip sense */
+ regmap_read(cs42l84->regmap, CS42L84_TSRS_PLUG_STATUS, ®);
+ cs42l84->plug_state = (((char) reg) &
+ (CS42L84_TS_PLUG | CS42L84_TS_UNPLUG)) >>
+ CS42L84_TS_PLUG_SHIFT;
+
+ /* Set mic-detection threshold */
+ regmap_update_bits(cs42l84->regmap,
+ CS42L84_MIC_DET_CTL1, CS42L84_MIC_DET_CTL1_HS_DET_LEVEL,
+ FIELD_PREP(CS42L84_MIC_DET_CTL1_HS_DET_LEVEL, 0x2c)); /* ~1.9 V */
+
+ /* Disconnect HSBIAS (initially) */
+ regmap_write(cs42l84->regmap,
+ CS42L84_HS_SWITCH_CTL,
+ CS42L84_HS_SWITCH_CTL_REF_HS3 | \
+ CS42L84_HS_SWITCH_CTL_REF_HS4 | \
+ CS42L84_HS_SWITCH_CTL_HSB_FILT_HS3 | \
+ CS42L84_HS_SWITCH_CTL_HSB_FILT_HS4 | \
+ CS42L84_HS_SWITCH_CTL_GNDHS_HS3 | \
+ CS42L84_HS_SWITCH_CTL_GNDHS_HS4);
+ regmap_update_bits(cs42l84->regmap,
+ CS42L84_HS_DET_CTL2,
+ CS42L84_HS_DET_CTL2_SET | CS42L84_HS_DET_CTL2_CTL,
+ FIELD_PREP(CS42L84_HS_DET_CTL2_SET, 2) |
+ FIELD_PREP(CS42L84_HS_DET_CTL2_CTL, 0));
+ regmap_update_bits(cs42l84->regmap,
+ CS42L84_HS_CLAMP_DISABLE, 1, 1);
+
+}
+
+static int cs42l84_i2c_probe(struct i2c_client *i2c_client)
+{
+ struct cs42l84_private *cs42l84;
+ int ret, devid;
+ unsigned int reg;
+
+ cs42l84 = devm_kzalloc(&i2c_client->dev, sizeof(struct cs42l84_private),
+ GFP_KERNEL);
+ if (!cs42l84)
+ return -ENOMEM;
+
+ cs42l84->dev = &i2c_client->dev;
+ i2c_set_clientdata(i2c_client, cs42l84);
+ mutex_init(&cs42l84->irq_lock);
+
+ cs42l84->regmap = devm_regmap_init_i2c(i2c_client, &cs42l84_regmap);
+ if (IS_ERR(cs42l84->regmap)) {
+ ret = PTR_ERR(cs42l84->regmap);
+ dev_err(&i2c_client->dev, "regmap_init() failed: %d\n", ret);
+ return ret;
+ }
+
+ /* Reset the Device */
+ cs42l84->reset_gpio = devm_gpiod_get_optional(&i2c_client->dev,
+ "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(cs42l84->reset_gpio)) {
+ ret = PTR_ERR(cs42l84->reset_gpio);
+ goto err_disable_noreset;
+ }
+
+ if (cs42l84->reset_gpio) {
+ dev_dbg(&i2c_client->dev, "Found reset GPIO\n");
+ gpiod_set_value_cansleep(cs42l84->reset_gpio, 1);
+ }
+ usleep_range(CS42L84_BOOT_TIME_US, CS42L84_BOOT_TIME_US * 2);
+
+ /* Request IRQ if one was specified */
+ if (i2c_client->irq) {
+ ret = request_threaded_irq(i2c_client->irq,
+ NULL, cs42l84_irq_thread,
+ IRQF_ONESHOT,
+ "cs42l84", cs42l84);
+ if (ret == -EPROBE_DEFER) {
+ goto err_disable_noirq;
+ } else if (ret != 0) {
+ dev_err(&i2c_client->dev,
+ "Failed to request IRQ: %d\n", ret);
+ goto err_disable_noirq;
+ }
+ }
+
+ /* initialize codec */
+ devid = cirrus_read_device_id(cs42l84->regmap, CS42L84_DEVID);
+ if (devid < 0) {
+ ret = devid;
+ dev_err(&i2c_client->dev, "Failed to read device ID: %d\n", ret);
+ goto err_disable;
+ }
+
+ if (devid != CS42L84_CHIP_ID) {
+ dev_err(&i2c_client->dev,
+ "CS42L84 Device ID (%X). Expected %X\n",
+ devid, CS42L84_CHIP_ID);
+ ret = -EINVAL;
+ goto err_disable;
+ }
+
+ ret = regmap_read(cs42l84->regmap, CS42L84_REVID, ®);
+ if (ret < 0) {
+ dev_err(&i2c_client->dev, "Get Revision ID failed\n");
+ goto err_shutdown;
+ }
+
+ dev_info(&i2c_client->dev,
+ "Cirrus Logic CS42L84, Revision: %02X\n", reg & 0xFF);
+
+ /* Setup plug detection */
+ cs42l84_setup_plug_detect(cs42l84);
+
+ /* Mask/Unmask Interrupts */
+ cs42l84_set_interrupt_masks(cs42l84);
+
+ /* Register codec for machine driver */
+ ret = devm_snd_soc_register_component(&i2c_client->dev,
+ &soc_component_dev_cs42l84, &cs42l84_dai, 1);
+ if (ret < 0)
+ goto err_shutdown;
+
+ return 0;
+
+err_shutdown:
+ /* Nothing to do */
+
+err_disable:
+ if (i2c_client->irq)
+ free_irq(i2c_client->irq, cs42l84);
+
+err_disable_noirq:
+ gpiod_set_value_cansleep(cs42l84->reset_gpio, 0);
+err_disable_noreset:
+ return ret;
+}
+
+static void cs42l84_i2c_remove(struct i2c_client *i2c_client)
+{
+ struct cs42l84_private *cs42l84 = i2c_get_clientdata(i2c_client);
+
+ if (i2c_client->irq)
+ free_irq(i2c_client->irq, cs42l84);
+
+ gpiod_set_value_cansleep(cs42l84->reset_gpio, 0);
+}
+
+static const struct of_device_id cs42l84_of_match[] = {
+ { .compatible = "cirrus,cs42l84", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, cs42l84_of_match);
+
+static const struct i2c_device_id cs42l84_id[] = {
+ {"cs42l84", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, cs42l84_id);
+
+static struct i2c_driver cs42l84_i2c_driver = {
+ .driver = {
+ .name = "cs42l84",
+ .of_match_table = of_match_ptr(cs42l84_of_match),
+ },
+ .id_table = cs42l84_id,
+ .probe = cs42l84_i2c_probe,
+ .remove = cs42l84_i2c_remove,
+};
+
+module_i2c_driver(cs42l84_i2c_driver);
+
+MODULE_DESCRIPTION("ASoC CS42L84 driver");
+MODULE_AUTHOR("Martin Povišer <povik+lin@cutebit.org>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/cs42l84.h b/sound/soc/codecs/cs42l84.h
new file mode 100644
index 0000000..35bd15e
--- /dev/null
+++ b/sound/soc/codecs/cs42l84.h
@@ -0,0 +1,218 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) The Asahi Linux Contributors
+ *
+ * Based on sound/soc/codecs/cs42l42.h
+ *
+ * Copyright 2016 Cirrus Logic, Inc.
+ */
+
+
+#ifndef __CS42L84_H__
+#define __CS42L84_H__
+
+#include <linux/bits.h>
+
+#define CS42L84_CHIP_ID 0x42a84
+
+#define CS42L84_DEVID 0x0000
+#define CS42L84_REVID 0x73fe
+#define CS42L84_FRZ_CTL 0x0006
+#define CS42L84_FRZ_CTL_ENGAGE BIT(0)
+
+#define CS42L84_TSRS_PLUG_INT_STATUS 0x0400
+#define CS42L84_TSRS_PLUG_INT_MASK 0x0418
+#define CS42L84_RS_PLUG_SHIFT 0
+#define CS42L84_RS_PLUG BIT(0)
+#define CS42L84_RS_UNPLUG BIT(1)
+#define CS42L84_TS_PLUG_SHIFT 2
+#define CS42L84_TS_PLUG BIT(2)
+#define CS42L84_TS_UNPLUG BIT(3)
+#define CS42L84_TSRS_PLUG_VAL_MASK GENMASK(3, 0)
+#define CS42L84_PLL_LOCK_STATUS 0x040e // probably bit 0x10
+#define CS42L84_PLL_LOCK_STATUS_LOCKED BIT(4)
+#define CS42L84_PLL_LOCK_STATUS_ERROR BIT(5)
+
+#define CS42L84_PLUG 3
+#define CS42L84_UNPLUG 0
+#define CS42L84_TRANS 1
+
+#if 0
+ l84.regs.RING_SENSE_CTRL.set(INV=1, UNK1=1,
+ RISETIME=E_DEBOUNCE_TIME.T_125MS, FALLTIME=E_DEBOUNCE_TIME.T_125MS)
+ l84.regs.TIP_SENSE_CTRL.set(INV=1,
+ RISETIME=E_DEBOUNCE_TIME.T_500MS, FALLTIME=E_DEBOUNCE_TIME.T_125MS)
+ l84.regs.MSM_BLOCK_EN3.set(TR_SENSE_EN=1)
+#endif
+
+#define CS42L84_CCM_CTL1 0x0600
+#define CS42L84_CCM_CTL1_MCLK_SRC GENMASK(1, 0)
+#define CS42L84_CCM_CTL1_MCLK_SRC_RCO 0
+#define CS42L84_CCM_CTL1_MCLK_SRC_MCLK 1
+#define CS42L84_CCM_CTL1_MCLK_SRC_BCLK 2
+#define CS42L84_CCM_CTL1_MCLK_SRC_PLL 3
+#define CS42L84_CCM_CTL1_MCLK_FREQ GENMASK(3, 2)
+#define CS42L84_CCM_CTL1_MCLK_F_12MHZ 0b00
+#define CS42L84_CCM_CTL1_MCLK_F_24MHZ 0b01
+#define CS42L84_CCM_CTL1_MCLK_F_12_288KHZ 0b10
+#define CS42L84_CCM_CTL1_MCLK_F_24_576KHZ 0b11
+#define CS42L84_CCM_CTL1_RCO \
+ (FIELD_PREP(CS42L84_CCM_CTL1_MCLK_SRC, CS42L84_CCM_CTL1_MCLK_SRC_RCO) \
+ | FIELD_PREP(CS42L84_CCM_CTL1_MCLK_FREQ, CS42L84_CCM_CTL1_MCLK_F_12MHZ))
+
+#define CS42L84_CCM_SAMP_RATE 0x0601
+#define CS42L84_CCM_SAMP_RATE_RATE_48KHZ 4
+#define CS42L84_CCM_SAMP_RATE_RATE_96KHZ 5
+#define CS42L84_CCM_SAMP_RATE_RATE_192KHZ 6
+#define CS42L84_CCM_SAMP_RATE_RATE_44K1HZ 12
+#define CS42L84_CCM_SAMP_RATE_RATE_88K2HZ 13
+#define CS42L84_CCM_SAMP_RATE_RATE_176K4HZ 14
+#define CS42L84_CCM_CTL3 0x0602
+#define CS42L84_CCM_CTL3_REFCLK_DIV GENMASK(2, 1)
+#define CS42L84_CCM_CTL4 0x0603
+#define CS42L84_CCM_CTL4_REFCLK_EN BIT(0)
+
+#define CS42L84_CCM_ASP_CLK_CTRL 0x0608
+
+#define CS42L84_PLL_CTL1 0x0800
+#define CS42L84_PLL_CTL1_EN BIT(0)
+#define CS42L84_PLL_CTL1_MODE GENMASK(2, 1)
+#define CS42L84_PLL_DIV_FRAC0 0x0804
+#define CS42L84_PLL_DIV_FRAC1 0x0805
+#define CS42L84_PLL_DIV_FRAC2 0x0806
+#define CS42L84_PLL_DIV_INT 0x0807
+#define CS42L84_PLL_DIVOUT 0x0808
+
+#define CS42L84_RING_SENSE_CTL 0x1282
+#define CS42L84_RING_SENSE_CTL_INV BIT(7)
+#define CS42L84_RING_SENSE_CTL_UNK1 BIT(6)
+#define CS42L84_RING_SENSE_CTL_FALLTIME GENMASK(5, 3)
+#define CS42L84_RING_SENSE_CTL_RISETIME GENMASK(2, 0)
+#define CS42L84_TIP_SENSE_CTL 0x1283
+#define CS42L84_TIP_SENSE_CTL_INV BIT(7)
+#define CS42L84_TIP_SENSE_CTL_FALLTIME GENMASK(5, 3)
+#define CS42L84_TIP_SENSE_CTL_RISETIME GENMASK(2, 0)
+
+#define CS42L84_TSRS_PLUG_STATUS 0x1288
+
+#define CS42L84_TIP_SENSE_CTL2 0x1473
+#define CS42L84_TIP_SENSE_CTL2_MODE GENMASK(7, 6)
+#define CS42L84_TIP_SENSE_CTL2_MODE_DISABLED 0b00
+#define CS42L84_TIP_SENSE_CTL2_MODE_DIG_INPUT 0b01
+#define CS42L84_TIP_SENSE_CTL2_MODE_SHORT_DET 0b11
+#define CS42L84_TIP_SENSE_CTL2_INV BIT(5)
+
+#define CS42L84_MISC_DET_CTL 0x1474
+#define CS42L84_MISC_DET_CTL_DETECT_MODE GENMASK(4, 3)
+#define CS42L84_MISC_DET_CTL_HSBIAS_CTL GENMASK(2, 1)
+#define CS42L84_MISC_DET_CTL_PDN_MIC_LVL_DET BIT(0)
+
+#define CS42L84_MIC_DET_CTL1 0x1475
+#define CS42L84_MIC_DET_CTL1_HS_DET_LEVEL GENMASK(5, 0)
+
+#define CS42L84_MIC_DET_CTL4 0x1477
+#define CS42L84_MIC_DET_CTL4_LATCH_TO_VP BIT(1)
+
+#define CS42L84_HS_DET_STATUS2 0x147d
+
+#define CS42L84_MSM_BLOCK_EN1 0x1800
+#define CS42L84_MSM_BLOCK_EN2 0x1801
+#define CS42L84_MSM_BLOCK_EN2_ASP_SHIFT 6
+#define CS42L84_MSM_BLOCK_EN2_BUS_SHIFT 5
+#define CS42L84_MSM_BLOCK_EN2_DAC_SHIFT 4
+#define CS42L84_MSM_BLOCK_EN2_ADC_SHIFT 3
+#define CS42L84_MSM_BLOCK_EN3 0x1802
+#define CS42L84_MSM_BLOCK_EN3_TR_SENSE BIT(3)
+
+#define CS42L84_HS_DET_CTL2 0x1811
+#define CS42L84_HS_DET_CTL2_CTL GENMASK(7, 6)
+#define CS42L84_HS_DET_CTL2_SET GENMASK(5, 4)
+#define CS42L84_HS_DET_CTL2_REF BIT(3)
+#define CS42L84_HS_DET_CTL2_AUTO_TIME GENMASK(1, 0)
+
+#define CS42L84_HS_SWITCH_CTL 0x1812
+#define CS42L84_HS_SWITCH_CTL_REF_HS3 BIT(7)
+#define CS42L84_HS_SWITCH_CTL_REF_HS4 BIT(6)
+#define CS42L84_HS_SWITCH_CTL_HSB_FILT_HS3 BIT(5)
+#define CS42L84_HS_SWITCH_CTL_HSB_FILT_HS4 BIT(4)
+#define CS42L84_HS_SWITCH_CTL_HSB_HS3 BIT(3)
+#define CS42L84_HS_SWITCH_CTL_HSB_HS4 BIT(2)
+#define CS42L84_HS_SWITCH_CTL_GNDHS_HS3 BIT(1)
+#define CS42L84_HS_SWITCH_CTL_GNDHS_HS4 BIT(0)
+
+#define CS42L84_HS_CLAMP_DISABLE 0x1813
+
+#define CS42L84_ADC_CTL1 0x2000
+#define CS42L84_ADC_CTL1_PREAMP_GAIN_SHIFT 6
+#define CS42L84_ADC_CTL1_PGA_GAIN_SHIFT 0
+#define CS42L84_ADC_CTL4 0x2003
+#define CS42L84_ADC_CTL4_WNF_CF_SHIFT 4
+#define CS42L84_ADC_CTL4_WNF_EN_SHIFT 3
+#define CS42L84_ADC_CTL4_HPF_CF_SHIFT 1
+#define CS42L84_ADC_CTL4_HPF_EN_SHIFT 0
+
+#define CS42L84_DAC_CTL1 0x3000
+#define CS42L84_DAC_CTL1_UNMUTE BIT(0)
+//#define CS42L84_DAC_CTL1_DACB_INV_SHIFT 1
+//#define CS42L84_DAC_CTL1_DACA_INV_SHIFT 0
+#define CS42L84_DAC_CTL2 0x3001
+
+#define CS42L84_DAC_CHA_VOL_LSB 0x3004
+#define CS42L84_DAC_CHA_VOL_MSB 0x3005
+#define CS42L84_DAC_CHB_VOL_LSB 0x3006
+#define CS42L84_DAC_CHB_VOL_MSB 0x3007
+#define CS42L84_HP_VOL_CTL 0x3020
+#define CS42L84_HP_VOL_CTL_ZERO_CROSS BIT(1)
+#define CS42L84_HP_VOL_CTL_SOFT BIT(0)
+
+#define CS42L84_SRC_ASP_RX_CH1 0b1101
+#define CS42L84_SRC_ASP_RX_CH2 0b1110
+
+#define CS42L84_BUS_ASP_TX_SRC 0x4000
+#define CS42L84_BUS_ASP_TX_SRC_CH1_SHIFT 0
+#define CS42L84_BUS_DAC_SRC 0x4001
+#define CS42L84_BUS_DAC_SRC_DACA_SHIFT 0
+#define CS42L84_BUS_DAC_SRC_DACB_SHIFT 4
+
+#define CS42L84_ASP_CTL 0x5000
+#define CS42L84_ASP_CTL_BCLK_EN_SHIFT 1
+#define CS42L84_ASP_CTL_TDM_MODE BIT(2)
+#define CS42L84_ASP_FSYNC_CTL2 0x5010
+#define CS42L84_ASP_FSYNC_CTL2_BCLK_PERIOD_LO GENMASK(7, 1)
+#define CS42L84_ASP_FSYNC_CTL3 0x5011
+#define CS42L84_ASP_FSYNC_CTL3_BCLK_PERIOD_HI GENMASK(4, 0)
+#define CS42L84_ASP_DATA_CTL 0x5018
+
+#define CS42L84_ASP_RX_EN 0x5020
+#define CS42L84_ASP_RX_EN_CH1_SHIFT 0
+#define CS42L84_ASP_RX_EN_CH2_SHIFT 1
+#define CS42L84_ASP_TX_EN 0x5024
+#define CS42L84_ASP_TX_EN_CH1_SHIFT 0
+
+#define CS42L84_ASP_RX_CH1_CTL1 0x5028
+#define CS42L84_ASP_RX_CH1_CTL2 0x5029
+#define CS42L84_ASP_RX_CH1_WIDTH 0x502a
+#define CS42L84_ASP_RX_CH2_CTL1 0x502c
+#define CS42L84_ASP_RX_CH2_CTL2 0x502d
+#define CS42L84_ASP_RX_CH2_WIDTH 0x502e
+
+#define CS42L84_ASP_RX_CHx_CTL1_EDGE BIT(0)
+#define CS42L84_ASP_RX_CHx_CTL1_SLOT_START_LSB GENMASK(7, 1)
+#define CS42L84_ASP_RX_CHx_CTL2_SLOT_START_MSB GENMASK(2, 0)
+
+#define CS42L84_ASP_TX_CH1_CTL1 0x5068
+#define CS42L84_ASP_TX_CH1_CTL2 0x5069
+#define CS42L84_ASP_TX_CH1_WIDTH 0x506a
+#define CS42L84_ASP_TX_CH2_CTL1 0x506c
+#define CS42L84_ASP_TX_CH2_CTL2 0x506d
+#define CS42L84_ASP_TX_CH2_WIDTH 0x506e
+
+#define CS42L84_DEBOUNCE_TIME_125MS 0b001
+#define CS42L84_DEBOUNCE_TIME_500MS 0b011
+
+#define CS42L84_BOOT_TIME_US 3000
+#define CS42L84_CLOCK_SWITCH_DELAY_US 150
+#define CS42L84_PLL_LOCK_POLL_US 250
+#define CS42L84_PLL_LOCK_TIMEOUT_US 1250
+
+#endif /* __CS42L84_H__ */
diff --git a/sound/soc/codecs/tas2764.c b/sound/soc/codecs/tas2764.c
index a9838e0..7b9cb76 100644
--- a/sound/soc/codecs/tas2764.c
+++ b/sound/soc/codecs/tas2764.c
@@ -16,6 +16,7 @@
#include <linux/regmap.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
+#include <linux/of_device.h>
#include <linux/slab.h>
#include <sound/soc.h>
#include <sound/pcm.h>
@@ -25,6 +26,11 @@
#include "tas2764.h"
+enum tas2764_devid {
+ DEVID_TAS2764 = 0,
+ DEVID_SN012776 = 1
+};
+
struct tas2764_priv {
struct snd_soc_component *component;
struct gpio_desc *reset_gpio;
@@ -32,7 +38,8 @@ struct tas2764_priv {
struct regmap *regmap;
struct device *dev;
int irq;
-
+ enum tas2764_devid devid;
+
int v_sense_slot;
int i_sense_slot;
@@ -528,10 +535,16 @@ static struct snd_soc_dai_driver tas2764_dai_driver[] = {
},
};
+static uint8_t sn012776_bop_presets[] = {
+ 0x01, 0x32, 0x02, 0x22, 0x83, 0x2d, 0x80, 0x02, 0x06,
+ 0x32, 0x46, 0x30, 0x02, 0x06, 0x38, 0x40, 0x30, 0x02,
+ 0x06, 0x3e, 0x37, 0x30, 0xff, 0xe6
+};
+
static int tas2764_codec_probe(struct snd_soc_component *component)
{
struct tas2764_priv *tas2764 = snd_soc_component_get_drvdata(component);
- int ret;
+ int ret, i;
tas2764->component = component;
@@ -580,6 +593,23 @@ static int tas2764_codec_probe(struct snd_soc_component *component)
if (ret < 0)
return ret;
+ if (tas2764->devid == DEVID_SN012776) {
+ ret = snd_soc_component_update_bits(component, TAS2764_PWR_CTRL,
+ TAS2764_PWR_CTRL_BOP_SRC,
+ TAS2764_PWR_CTRL_BOP_SRC);
+ if (ret < 0)
+ return ret;
+
+ for (i = 0; i < ARRAY_SIZE(sn012776_bop_presets); i++) {
+ ret = snd_soc_component_write(component,
+ TAS2764_BOP_CFG0 + i,
+ sn012776_bop_presets[i]);
+
+ if (ret < 0)
+ return ret;
+ }
+ }
+
return 0;
}
@@ -595,12 +625,21 @@ static SOC_ENUM_SINGLE_DECL(
tas2764_hpf_enum, TAS2764_DC_BLK0,
TAS2764_DC_BLK0_HPF_FREQ_PB_SHIFT, tas2764_hpf_texts);
+static const char * const tas2764_oce_texts[] = {
+ "Disable", "Retry",
+};
+
+static SOC_ENUM_SINGLE_DECL(
+ tas2764_oce_enum, TAS2764_MISC_CFG1,
+ TAS2764_MISC_CFG1_OCE_RETRY_SHIFT, tas2764_oce_texts);
+
static const struct snd_kcontrol_new tas2764_snd_controls[] = {
SOC_SINGLE_TLV("Speaker Volume", TAS2764_DVC, 0,
TAS2764_DVC_MAX, 1, tas2764_playback_volume),
SOC_SINGLE_TLV("Amp Gain Volume", TAS2764_CHNL_0, 1, 0x14, 0,
tas2764_digital_tlv),
SOC_ENUM("HPF Corner Frequency", tas2764_hpf_enum),
+ SOC_ENUM("OCE Handling", tas2764_oce_enum),
};
static const struct snd_soc_component_driver soc_component_driver_tas2764 = {
@@ -699,9 +738,12 @@ static int tas2764_parse_dt(struct device *dev, struct tas2764_priv *tas2764)
return 0;
}
+static const struct of_device_id tas2764_of_match[];
+
static int tas2764_i2c_probe(struct i2c_client *client)
{
struct tas2764_priv *tas2764;
+ const struct of_device_id *of_id = NULL;
int result;
tas2764 = devm_kzalloc(&client->dev, sizeof(struct tas2764_priv),
@@ -709,6 +751,14 @@ static int tas2764_i2c_probe(struct i2c_client *client)
if (!tas2764)
return -ENOMEM;
+ if (client->dev.of_node)
+ of_id = of_match_device(tas2764_of_match, &client->dev);
+
+ if (of_id)
+ tas2764->devid = (enum tas2764_devid) of_id->data;
+ else
+ tas2764->devid = DEVID_TAS2764;
+
tas2764->dev = &client->dev;
tas2764->irq = client->irq;
i2c_set_clientdata(client, tas2764);
@@ -743,13 +793,12 @@ static const struct i2c_device_id tas2764_i2c_id[] = {
};
MODULE_DEVICE_TABLE(i2c, tas2764_i2c_id);
-#if defined(CONFIG_OF)
static const struct of_device_id tas2764_of_match[] = {
- { .compatible = "ti,tas2764" },
+ { .compatible = "ti,tas2764", .data = (void*) DEVID_TAS2764 },
+ { .compatible = "ti,sn012776", .data = (void*) DEVID_SN012776 },
{},
};
MODULE_DEVICE_TABLE(of, tas2764_of_match);
-#endif
static struct i2c_driver tas2764_i2c_driver = {
.driver = {
diff --git a/sound/soc/codecs/tas2764.h b/sound/soc/codecs/tas2764.h
index 168af77..20628e5 100644
--- a/sound/soc/codecs/tas2764.h
+++ b/sound/soc/codecs/tas2764.h
@@ -29,6 +29,7 @@
#define TAS2764_PWR_CTRL_ACTIVE 0x0
#define TAS2764_PWR_CTRL_MUTE BIT(0)
#define TAS2764_PWR_CTRL_SHUTDOWN BIT(1)
+#define TAS2764_PWR_CTRL_BOP_SRC BIT(7)
#define TAS2764_VSENSE_POWER_EN 3
#define TAS2764_ISENSE_POWER_EN 4
@@ -43,6 +44,10 @@
#define TAS2764_CHNL_0 TAS2764_REG(0X0, 0x03)
+/* Miscellaneous */
+#define TAS2764_MISC_CFG1 TAS2764_REG(0x0, 0x06)
+#define TAS2764_MISC_CFG1_OCE_RETRY_SHIFT 5
+
/* TDM Configuration Reg0 */
#define TAS2764_TDM_CFG0 TAS2764_REG(0X0, 0x08)
#define TAS2764_TDM_CFG0_SMP_MASK BIT(5)
@@ -110,4 +115,6 @@
#define TAS2764_INT_CLK_CFG TAS2764_REG(0x0, 0x5c)
#define TAS2764_INT_CLK_CFG_IRQZ_CLR BIT(2)
+#define TAS2764_BOP_CFG0 TAS2764_REG(0X0, 0x1d)
+
#endif /* __TAS2764__ */
diff --git a/sound/soc/soc-card.c b/sound/soc/soc-card.c
index 285ab4c..674a1225 100644
--- a/sound/soc/soc-card.c
+++ b/sound/soc/soc-card.c
@@ -197,10 +197,16 @@ int snd_soc_card_late_probe(struct snd_soc_card *card)
return 0;
}
-void snd_soc_card_fixup_controls(struct snd_soc_card *card)
+int snd_soc_card_fixup_controls(struct snd_soc_card *card)
{
- if (card->fixup_controls)
- card->fixup_controls(card);
+ if (card->fixup_controls) {
+ int ret = card->fixup_controls(card);
+
+ if (ret < 0)
+ return soc_card_ret(card, ret);
+ }
+
+ return 0;
}
int snd_soc_card_remove(struct snd_soc_card *card)
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c
index b2bd45e..6773407 100644
--- a/sound/soc/soc-core.c
+++ b/sound/soc/soc-core.c
@@ -2171,7 +2171,10 @@ static int snd_soc_bind_card(struct snd_soc_card *card)
goto probe_end;
snd_soc_dapm_new_widgets(card);
- snd_soc_card_fixup_controls(card);
+
+ ret = snd_soc_card_fixup_controls(card);
+ if (ret < 0)
+ goto probe_end;
ret = snd_card_register(card->snd_card);
if (ret < 0) {
diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c
index 3844f77..6f3d978 100644
--- a/sound/soc/soc-dapm.c
+++ b/sound/soc/soc-dapm.c
@@ -2195,6 +2195,139 @@ static const struct file_operations dapm_bias_fops = {
.llseek = default_llseek,
};
+static ssize_t dapm_graph_read_file(struct file *file, char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct snd_soc_card *card = file->private_data;
+ struct snd_soc_dapm_context *dapm;
+ struct snd_soc_dapm_path *p;
+ struct snd_soc_dapm_widget *w;
+ struct snd_soc_pcm_runtime *rtd;
+ struct snd_soc_dapm_widget *wdone[16];
+ struct snd_soc_dai *dai;
+ int i, num_wdone = 0, cluster = 0;
+ char *buf;
+ ssize_t bufsize;
+ ssize_t ret = 0;
+
+ bufsize = 1024 * card->num_dapm_widgets;
+ buf = kmalloc(bufsize, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ mutex_lock(&card->dapm_mutex);
+
+#define bufprintf(...) \
+ ret += scnprintf(buf + ret, bufsize - ret, __VA_ARGS__)
+
+ bufprintf("digraph dapm {\n");
+
+ /*
+ * Print the user-visible devices of the card.
+ */
+ bufprintf("subgraph cluster_%d {\n", cluster++);
+ bufprintf("label=\"Devices\";style=filled;fillcolor=gray;\n");
+ for_each_card_rtds(card, rtd) {
+ if (rtd->dai_link->no_pcm)
+ continue;
+
+ bufprintf("w%pK [label=\"%d: %s\"];\n", rtd,
+ rtd->pcm->device, rtd->dai_link->name);
+ }
+ bufprintf("};\n");
+
+ /*
+ * Print the playback/capture widgets of DAIs just next to
+ * the user-visible devices. Keep the list of already printed
+ * widgets in 'wdone', so they will be skipped later.
+ */
+ for_each_card_rtds(card, rtd) {
+ for_each_rtd_cpu_dais(rtd, i, dai) {
+ if (dai->stream[SNDRV_PCM_STREAM_PLAYBACK].widget) {
+ w = dai->stream[SNDRV_PCM_STREAM_PLAYBACK].widget;
+ bufprintf("w%pK [label=\"%s\"];\n", w, w->name);
+ if (!rtd->dai_link->no_pcm)
+ bufprintf("w%pK -> w%pK;\n", rtd, w);
+ wdone[num_wdone] = w;
+ if (num_wdone < ARRAY_SIZE(wdone))
+ num_wdone++;
+ }
+
+ if (dai->stream[SNDRV_PCM_STREAM_CAPTURE].widget) {
+ w = dai->stream[SNDRV_PCM_STREAM_CAPTURE].widget;
+ bufprintf("w%pK [label=\"%s\"];\n", w, w->name);
+ if (!rtd->dai_link->no_pcm)
+ bufprintf("w%pK -> w%pK;\n", w, rtd);
+ wdone[num_wdone] = w;
+ if (num_wdone < ARRAY_SIZE(wdone))
+ num_wdone++;
+ }
+ }
+ }
+
+ for_each_card_dapms(card, dapm) {
+ const char *prefix = soc_dapm_prefix(dapm);
+
+ if (dapm != &card->dapm) {
+ bufprintf("subgraph cluster_%d {\n", cluster++);
+ if (prefix)
+ bufprintf("label=\"%s\";\n", prefix);
+ else if (dapm->component)
+ bufprintf("label=\"%s\";\n",
+ dapm->component->name);
+ }
+
+ for_each_card_widgets(dapm->card, w) {
+ const char *name = w->name;
+ bool skip = false;
+
+ if (w->dapm != dapm)
+ continue;
+
+ if (list_empty(&w->edges[0]) && list_empty(&w->edges[1]))
+ continue;
+
+ for (i = 0; i < num_wdone; i++)
+ if (wdone[i] == w)
+ skip = true;
+ if (skip)
+ continue;
+
+ if (prefix && strlen(name) > strlen(prefix) + 1)
+ name += strlen(prefix) + 1;
+
+ bufprintf("w%pK [label=\"%s\"];\n", w, name);
+ }
+
+ if (dapm != &card->dapm)
+ bufprintf("}\n");
+ }
+
+ list_for_each_entry(p, &card->paths, list) {
+ if (p->name)
+ bufprintf("w%pK -> w%pK [label=\"%s\"];\n",
+ p->source, p->sink, p->name);
+ else
+ bufprintf("w%pK -> w%pK;\n", p->source, p->sink);
+ }
+
+ bufprintf("}\n");
+#undef bufprintf
+
+ mutex_unlock(&card->dapm_mutex);
+
+ ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret);
+
+ kfree(buf);
+ return ret;
+}
+
+static const struct file_operations dapm_graph_fops = {
+ .open = simple_open,
+ .read = dapm_graph_read_file,
+ .llseek = default_llseek,
+};
+
void snd_soc_dapm_debugfs_init(struct snd_soc_dapm_context *dapm,
struct dentry *parent)
{
@@ -2205,6 +2338,10 @@ void snd_soc_dapm_debugfs_init(struct snd_soc_dapm_context *dapm,
debugfs_create_file("bias_level", 0444, dapm->debugfs_dapm, dapm,
&dapm_bias_fops);
+
+ if (dapm == &dapm->card->dapm)
+ debugfs_create_file("graph.dot", 0444, dapm->debugfs_dapm,
+ dapm->card, &dapm_graph_fops);
}
static void dapm_debugfs_add_widget(struct snd_soc_dapm_widget *w)
diff --git a/sound/soc/soc-ops.c b/sound/soc/soc-ops.c
index 55b009d..1103561 100644
--- a/sound/soc/soc-ops.c
+++ b/sound/soc/soc-ops.c
@@ -176,28 +176,20 @@ int snd_soc_info_volsw(struct snd_kcontrol *kcontrol,
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
- const char *vol_string = NULL;
- int max;
+ int platform_max;
- max = uinfo->value.integer.max = mc->max - mc->min;
- if (mc->platform_max && mc->platform_max < max)
- max = mc->platform_max;
+ if (!mc->platform_max)
+ mc->platform_max = mc->max;
+ platform_max = mc->platform_max;
- if (max == 1) {
- /* Even two value controls ending in Volume should always be integer */
- vol_string = strstr(kcontrol->id.name, " Volume");
- if (vol_string && !strcmp(vol_string, " Volume"))
- uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
- else
- uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
- } else {
+ if (platform_max == 1 && !strstr(kcontrol->id.name, " Volume"))
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ else
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
- }
uinfo->count = snd_soc_volsw_is_stereo(mc) ? 2 : 1;
uinfo->value.integer.min = 0;
- uinfo->value.integer.max = max;
-
+ uinfo->value.integer.max = platform_max - mc->min;
return 0;
}
EXPORT_SYMBOL_GPL(snd_soc_info_volsw);
@@ -639,37 +631,217 @@ int snd_soc_get_volsw_range(struct snd_kcontrol *kcontrol,
}
EXPORT_SYMBOL_GPL(snd_soc_get_volsw_range);
+static bool soc_control_matches(struct snd_kcontrol *kctl,
+ const char *pattern)
+{
+ const char *name = kctl->id.name;
+
+ if (pattern[0] == '*') {
+ int namelen;
+ int patternlen;
+
+ pattern++;
+ if (pattern[0] == ' ')
+ pattern++;
+
+ namelen = strlen(name);
+ patternlen = strlen(pattern);
+
+ if (namelen > patternlen)
+ name += namelen - patternlen;
+ }
+
+ return !strcmp(name, pattern);
+}
+
+static int soc_clip_to_platform_max(struct snd_kcontrol *kctl)
+{
+ struct soc_mixer_control *mc = (struct soc_mixer_control *)kctl->private_value;
+ struct snd_ctl_elem_value uctl;
+ int ret;
+
+ if (!mc->platform_max)
+ return 0;
+
+ ret = kctl->get(kctl, &uctl);
+ if (ret < 0)
+ return ret;
+
+ if (uctl.value.integer.value[0] > mc->platform_max)
+ uctl.value.integer.value[0] = mc->platform_max;
+
+ if (snd_soc_volsw_is_stereo(mc) &&
+ uctl.value.integer.value[1] > mc->platform_max)
+ uctl.value.integer.value[1] = mc->platform_max;
+
+ ret = kctl->put(kctl, &uctl);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int soc_limit_volume(struct snd_kcontrol *kctl, int max)
+{
+ struct soc_mixer_control *mc = (struct soc_mixer_control *)kctl->private_value;
+
+ if (max <= 0 || max > mc->max)
+ return -EINVAL;
+ mc->platform_max = max;
+
+ return soc_clip_to_platform_max(kctl);
+}
+
/**
- * snd_soc_limit_volume - Set new limit to an existing volume control.
+ * snd_soc_limit_volume - Set new limit to existing volume controls
*
* @card: where to look for the control
- * @name: Name of the control
+ * @name: name pattern
* @max: new maximum limit
+ *
+ * Finds controls matching the given name (which can be either a name
+ * verbatim, or a pattern starting with the wildcard '*') and sets
+ * a platform volume limit on them.
*
- * Return 0 for success, else error.
+ * Return number of matching controls on success, else error. At least
+ * one control needs to match the pattern.
*/
int snd_soc_limit_volume(struct snd_soc_card *card,
const char *name, int max)
{
struct snd_kcontrol *kctl;
- int ret = -EINVAL;
+ int hits = 0;
+ int ret;
- /* Sanity check for name and max */
- if (unlikely(!name || max <= 0))
+ /* Sanity check for name */
+ if (unlikely(!name))
return -EINVAL;
- kctl = snd_soc_card_get_kcontrol(card, name);
- if (kctl) {
- struct soc_mixer_control *mc = (struct soc_mixer_control *)kctl->private_value;
- if (max <= mc->max) {
- mc->platform_max = max;
- ret = 0;
- }
+ list_for_each_entry(kctl, &card->snd_card->controls, list) {
+ if (!soc_control_matches(kctl, name))
+ continue;
+
+ ret = soc_limit_volume(kctl, max);
+ if (ret < 0)
+ return ret;
+ hits++;
}
- return ret;
+
+ if (!hits)
+ return -EINVAL;
+
+ return hits;
}
EXPORT_SYMBOL_GPL(snd_soc_limit_volume);
+/**
+ * snd_soc_deactivate_kctl - Activate/deactive controls matching a pattern
+ *
+ * @card: where to look for the controls
+ * @name: name pattern
+ * @active: non-zero to activate, zero to deactivate
+ *
+ * Return number of matching controls on success, else error.
+ * No controls need to match.
+ */
+int snd_soc_deactivate_kctl(struct snd_soc_card *card,
+ const char *name, int active)
+{
+ struct snd_kcontrol *kctl;
+ int hits = 0;
+ int ret;
+
+ /* Sanity check for name */
+ if (unlikely(!name))
+ return -EINVAL;
+
+ list_for_each_entry(kctl, &card->snd_card->controls, list) {
+ if (!soc_control_matches(kctl, name))
+ continue;
+
+ ret = snd_ctl_activate_id(card->snd_card, &kctl->id, active);
+ if (ret < 0)
+ return ret;
+ hits++;
+ }
+
+ if (!hits)
+ return -EINVAL;
+
+ return hits;
+}
+EXPORT_SYMBOL_GPL(snd_soc_deactivate_kctl);
+
+static int soc_set_enum_kctl(struct snd_kcontrol *kctl, const char *strval)
+{
+ struct snd_ctl_elem_value value;
+ struct snd_ctl_elem_info info;
+ int sel, i, ret;
+
+ ret = kctl->info(kctl, &info);
+ if (ret < 0)
+ return ret;
+
+ if (info.type != SNDRV_CTL_ELEM_TYPE_ENUMERATED)
+ return -EINVAL;
+
+ for (sel = 0; sel < info.value.enumerated.items; sel++) {
+ info.value.enumerated.item = sel;
+ ret = kctl->info(kctl, &info);
+ if (ret < 0)
+ return ret;
+
+ if (!strcmp(strval, info.value.enumerated.name))
+ break;
+ }
+
+ if (sel == info.value.enumerated.items)
+ return -EINVAL;
+
+ for (i = 0; i < info.count; i++)
+ value.value.enumerated.item[i] = sel;
+
+ return kctl->put(kctl, &value);
+}
+
+/**
+ * snd_soc_set_enum_kctl - Set enumerated controls matching a pattern
+ *
+ * @card: where to look for the controls
+ * @name: name pattern
+ * @value: string value to set the controls to
+ *
+ * Return number of matching and set controls on success, else error.
+ * No controls need to match.
+ */
+int snd_soc_set_enum_kctl(struct snd_soc_card *card,
+ const char *name, const char *value)
+{
+ struct snd_kcontrol *kctl;
+ int hits = 0;
+ int ret;
+
+ /* Sanity check for name */
+ if (unlikely(!name))
+ return -EINVAL;
+
+ list_for_each_entry(kctl, &card->snd_card->controls, list) {
+ if (!soc_control_matches(kctl, name))
+ continue;
+
+ ret = soc_set_enum_kctl(kctl, value);
+ if (ret < 0)
+ return ret;
+ hits++;
+ }
+
+ if (!hits)
+ return -EINVAL;
+
+ return hits;
+}
+EXPORT_SYMBOL_GPL(snd_soc_set_enum_kctl);
+
int snd_soc_bytes_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{