From: Sven Peter <hidden> Date: 2021-09-26 09:59:41
Hi,
This series adds support for the I2C controller found on Apple Silicon Macs
which has quite a bit of history:
Apple bought P.A. Semi in 2008 and it looks like a part of its legacy continues
to live on in the M1. This controller has actually been used since at least the
iPhone 4S and hasn't changed much since then.
Essentially, there are only a few differences that matter:
- The controller no longer is a PCI device
- Starting at some iPhone an additional bit in one register
must be set in order to start transmissions.
- The reference clock and hence the clock dividers are different
In order to add support for a platform device I first replaced PCI-specific
bits and split out the PCI driver to its own file. Then I added support
to make the clock divider configurable and converted the driver to use
managed device resources to make it a bit simpler.
The Apple and PASemi driver will never be compiled in the same kernel
since the Apple one will run on arm64 while the original PASemi driver
will only be useful on powerpc.
I've thus followed the octeon (mips)/thunderx(arm64) approach to do the
split: I created a -core.c file which contains the shared logic and just
compile that one for both the PASemi and the new Apple driver.
Now unfortunately I don't have access to any old PASemi hardware and
cannot confirm that my changes haven't broken anything for those.
I believe Hector was in contact with Olof a few months ago who
said that he might still have an old machine on which he could
test this.
I'd very much appreciate if he (or anyone else for that matter :-))
could give this series a quick test on the old PASemi machines.
Best,
Sven
Sven Peter (10):
dt-bindings: i2c: Add Apple I2C controller bindings
i2c: pasemi: Use io{read,write}32
i2c: pasemi: Remove usage of pci_dev
i2c: pasemi: Split off common probing code
i2c: pasemi: Split pci driver to its own file
i2c: pasemi: Move common reset code to own function
i2c: pasemi: Allow to configure bus frequency
i2c: pasemi: Refactor _probe to use devm_*
i2c: pasemi: Add Apple platform driver
i2c: pasemi: Set enable bit for Apple variant
.../devicetree/bindings/i2c/apple,i2c.yaml | 61 +++++++++
MAINTAINERS | 2 +
drivers/i2c/busses/Kconfig | 11 ++
drivers/i2c/busses/Makefile | 3 +
drivers/i2c/busses/i2c-pasemi-apple.c | 122 ++++++++++++++++++
.../{i2c-pasemi.c => i2c-pasemi-core.c} | 114 +++++-----------
drivers/i2c/busses/i2c-pasemi-core.h | 21 +++
drivers/i2c/busses/i2c-pasemi-pci.c | 85 ++++++++++++
8 files changed, 334 insertions(+), 85 deletions(-)
create mode 100644 Documentation/devicetree/bindings/i2c/apple,i2c.yaml
create mode 100644 drivers/i2c/busses/i2c-pasemi-apple.c
rename drivers/i2c/busses/{i2c-pasemi.c => i2c-pasemi-core.c} (77%)
create mode 100644 drivers/i2c/busses/i2c-pasemi-core.h
create mode 100644 drivers/i2c/busses/i2c-pasemi-pci.c
--
2.25.1
From: Sven Peter <hidden> Date: 2021-09-26 09:59:56
The Apple I2C controller is based on the PASemi I2C controller.
It is present on Apple SoCs such as the M1.
Signed-off-by: Sven Peter <redacted>
---
.../devicetree/bindings/i2c/apple,i2c.yaml | 61 +++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 62 insertions(+)
create mode 100644 Documentation/devicetree/bindings/i2c/apple,i2c.yaml
@@ -0,0 +1,61 @@+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)+%YAML1.2+---+$id:"http://devicetree.org/schemas/i2c/apple,i2c.yaml#"+$schema:"http://devicetree.org/meta-schemas/core.yaml#"++title:Apple/PASemi I2C controller++maintainers:+-Sven Peter <sven@svenpeter.dev>++description:|+Apple SoCs such as the M1 come with a I2C controller based on the one found+in machines with P. A. Semi's PWRficient processors.+The bus is used to communicate with e.g. USB PD chips or the speaker+amp.++allOf:+-$ref:/schemas/i2c/i2c-controller.yaml#++properties:+compatible:+enum:+-apple,t8103-i2c+-apple,i2c++reg:+maxItems:1++clocks:+items:+-description:I2C bus reference clock++interrupts:+maxItems:1++clock-frequency:+description:|+Desired I2C bus clock frequency in Hz. If not specified, 100 kHz will be+used. This frequency is generated by dividing the reference clock.+Allowed values are between ref_clk/(16*4) and ref_clk/(16*255).++required:+-compatible+-reg+-clocks+-interrupts++unevaluatedProperties:false++examples:+-|+i2c@35010000 {+compatible = "apple,t8103-i2c";+reg = <0x35010000 0x4000>;+interrupt-parent = <&aic>;+interrupts = <0 627 4>;+clocks = <&ref_clk>;+#address-cells = <1>;+#size-cells = <0>;+};
From: Sven Peter <hidden> Date: 2021-09-26 09:59:56
Prepare to create a platform driver by removing all usages of pci_dev we
can.
Signed-off-by: Sven Peter <redacted>
---
drivers/i2c/busses/i2c-pasemi.c | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
@@ -52,7 +52,7 @@ struct pasemi_smbus {staticinlinevoidreg_write(structpasemi_smbus*smbus,intreg,intval){-dev_dbg(&smbus->dev->dev,"smbus write reg %lx val %08x\n",+dev_dbg(smbus->dev,"smbus write reg %lx val %08x\n",smbus->base+reg,val);iowrite32(val,smbus->ioaddr+reg);}
@@ -61,7 +61,7 @@ static inline int reg_read(struct pasemi_smbus *smbus, int reg){intret;ret=ioread32(smbus->ioaddr+reg);-dev_dbg(&smbus->dev->dev,"smbus read reg %lx val %08x\n",+dev_dbg(smbus->dev,"smbus read reg %lx val %08x\n",smbus->base+reg,ret);returnret;}
@@ -94,7 +94,7 @@ static int pasemi_smb_waitready(struct pasemi_smbus *smbus)return-ENXIO;if(timeout<0){-dev_warn(&smbus->dev->dev,"Timeout, status 0x%08x\n",status);+dev_warn(smbus->dev,"Timeout, status 0x%08x\n",status);reg_write(smbus,REG_SMSTA,status);return-ETIME;}
@@ -342,7 +342,7 @@ static int pasemi_smb_probe(struct pci_dev *dev,if(!smbus)return-ENOMEM;-smbus->dev=dev;+smbus->dev=&dev->dev;smbus->base=pci_resource_start(dev,0);smbus->size=pci_resource_len(dev,0);
@@ -366,7 +366,7 @@ static int pasemi_smb_probe(struct pci_dev *dev,smbus->adapter.algo_data=smbus;/* set up the sysfs linkage to our parent device */-smbus->adapter.dev.parent=&dev->dev;+smbus->adapter.dev.parent=smbus->dev;reg_write(smbus,REG_CTL,(CTL_MTR|CTL_MRR|(CLK_100K_DIV&CTL_CLK_M)));
From: Sven Peter <hidden> Date: 2021-09-26 09:59:56
In preparation for splitting this driver up into a platform_driver
and a pci_driver, replace outl/inl usage with ioport_map and
ioread32/iowrite32.
Signed-off-by: Sven Peter <redacted>
---
drivers/i2c/busses/i2c-pasemi.c | 16 +++++++++++++---
1 file changed, 13 insertions(+), 3 deletions(-)
From: Sven Peter <hidden> Date: 2021-09-26 09:59:56
Split off common probing code that will be used by both the PCI and the
platform device.
Signed-off-by: Sven Peter <redacted>
---
drivers/i2c/busses/i2c-pasemi.c | 39 +++++++++++++++++++++------------
1 file changed, 25 insertions(+), 14 deletions(-)
@@ -329,6 +329,30 @@ static const struct i2c_algorithm smbus_algorithm = {.functionality=pasemi_smb_func,};+staticintpasemi_i2c_common_probe(structpasemi_smbus*smbus)+{+interror;++smbus->adapter.owner=THIS_MODULE;+snprintf(smbus->adapter.name,sizeof(smbus->adapter.name),+"PA Semi SMBus adapter at 0x%lx",smbus->base);+smbus->adapter.class=I2C_CLASS_HWMON|I2C_CLASS_SPD;+smbus->adapter.algo=&smbus_algorithm;+smbus->adapter.algo_data=smbus;++/* set up the sysfs linkage to our parent device */+smbus->adapter.dev.parent=smbus->dev;++reg_write(smbus,REG_CTL,(CTL_MTR|CTL_MRR|+(CLK_100K_DIV&CTL_CLK_M)));++error=i2c_add_adapter(&smbus->adapter);+if(error)+returnerror;++return0;+}+staticintpasemi_smb_probe(structpci_dev*dev,conststructpci_device_id*id){
@@ -358,20 +382,7 @@ static int pasemi_smb_probe(struct pci_dev *dev,gotoout_release_region;}-smbus->adapter.owner=THIS_MODULE;-snprintf(smbus->adapter.name,sizeof(smbus->adapter.name),-"PA Semi SMBus adapter at 0x%lx",smbus->base);-smbus->adapter.class=I2C_CLASS_HWMON|I2C_CLASS_SPD;-smbus->adapter.algo=&smbus_algorithm;-smbus->adapter.algo_data=smbus;--/* set up the sysfs linkage to our parent device */-smbus->adapter.dev.parent=smbus->dev;--reg_write(smbus,REG_CTL,(CTL_MTR|CTL_MRR|-(CLK_100K_DIV&CTL_CLK_M)));--error=i2c_add_adapter(&smbus->adapter);+interror=pasemi_i2c_common_probe(smbus);if(error)gotoout_ioport_unmap;
diff --git a/drivers/i2c/busses/i2c-pasemi.c b/drivers/i2c/busses/i2c-pasemi-core.csimilarity index 81%rename from drivers/i2c/busses/i2c-pasemi.crename to drivers/i2c/busses/i2c-pasemi-core.cindex 9d69ff63f674..3eda5e375fa2 100644--- a/drivers/i2c/busses/i2c-pasemi.c+++ b/drivers/i2c/busses/i2c-pasemi-core.c
From: Sven Peter <hidden> Date: 2021-09-26 10:00:02
Split out common reset call to its own function so that we
can later add support for selecting the clock frequency
and an additional enable bit found in newer revisions.
Signed-off-by: Sven Peter <redacted>
---
drivers/i2c/busses/i2c-pasemi-core.c | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
@@ -61,6 +61,12 @@ static inline int reg_read(struct pasemi_smbus *smbus, int reg)#define TXFIFO_WR(smbus, reg) reg_write((smbus), REG_MTXFIFO, (reg))#define RXFIFO_RD(smbus) reg_read((smbus), REG_MRXFIFO)+staticvoidpasemi_reset(structpasemi_smbus*smbus)+{+reg_write(smbus,REG_CTL,(CTL_MTR|CTL_MRR|+(CLK_100K_DIV&CTL_CLK_M)));+}+staticvoidpasemi_smb_clear(structpasemi_smbus*smbus){unsignedintstatus;
@@ -135,8 +141,7 @@ static int pasemi_i2c_xfer_msg(struct i2c_adapter *adapter,return0;reset_out:-reg_write(smbus,REG_CTL,(CTL_MTR|CTL_MRR|-(CLK_100K_DIV&CTL_CLK_M)));+pasemi_reset(smbus);returnerr;}
@@ -302,8 +307,7 @@ static int pasemi_smb_xfer(struct i2c_adapter *adapter,return0;reset_out:-reg_write(smbus,REG_CTL,(CTL_MTR|CTL_MRR|-(CLK_100K_DIV&CTL_CLK_M)));+pasemi_reset(smbus);returnerr;}
@@ -335,8 +339,7 @@ int pasemi_i2c_common_probe(struct pasemi_smbus *smbus)/* set up the sysfs linkage to our parent device */smbus->adapter.dev.parent=smbus->dev;-reg_write(smbus,REG_CTL,(CTL_MTR|CTL_MRR|-(CLK_100K_DIV&CTL_CLK_M)));+pasemi_reset(smbus);error=i2c_add_adapter(&smbus->adapter);if(error)
From: Sven Peter <hidden> Date: 2021-09-26 10:00:04
Right now the bus frequency has always been hardcoded as
100 KHz with the specific reference clock used in the PASemi
PCI controllers. Make this configurable to prepare for the
platform driver.
Signed-off-by: Sven Peter <redacted>
---
drivers/i2c/busses/i2c-pasemi-core.c | 8 +++-----
drivers/i2c/busses/i2c-pasemi-core.h | 1 +
drivers/i2c/busses/i2c-pasemi-pci.c | 4 ++++
3 files changed, 8 insertions(+), 5 deletions(-)
From: Sven Peter <hidden> Date: 2021-09-26 10:00:06
Using managed device resources means there's nothing left to be done in
pasemi_smb_pci_remove and also allows to remove base and size from
struct pasemi_smbus.
Signed-off-by: Sven Peter <redacted>
---
drivers/i2c/busses/i2c-pasemi-core.c | 10 +++----
drivers/i2c/busses/i2c-pasemi-core.h | 2 --
drivers/i2c/busses/i2c-pasemi-pci.c | 45 ++++++++--------------------
3 files changed, 16 insertions(+), 41 deletions(-)
From: Sven Peter <hidden> Date: 2021-09-26 10:00:12
With all the previous preparations we can now finally add
the platform driver to support the PASemi-based controllers
in Apple SoCs. This does not work on the M1 yet but should
work on the early iPhones already.
Signed-off-by: Sven Peter <redacted>
---
MAINTAINERS | 1 +
drivers/i2c/busses/Kconfig | 11 +++
drivers/i2c/busses/Makefile | 2 +
drivers/i2c/busses/i2c-pasemi-apple.c | 122 ++++++++++++++++++++++++++
4 files changed, 136 insertions(+)
create mode 100644 drivers/i2c/busses/i2c-pasemi-apple.c
@@ -0,0 +1,122 @@+// SPDX-License-Identifier: GPL-2.0-only+/*+*Copyright(C)2021TheAsahiLinuxContributors+*+*PASemiPWRficientSMBushostdriverforAppleSoCs+*/++#include<linux/clk.h>+#include<linux/i2c.h>+#include<linux/io.h>+#include<linux/kernel.h>+#include<linux/module.h>+#include<linux/platform_device.h>+#include<linux/types.h>++#include"i2c-pasemi-core.h"++structpasemi_apple_i2c_data{+structpasemi_smbussmbus;+structclk*clk_ref;+structpinctrl*pctrl;+};++staticintpasemi_apple_i2c_calc_clk_div(structpasemi_apple_i2c_data*data,+u32frequency)+{+unsignedlongclk_rate=clk_get_rate(data->clk_ref);++if(!clk_rate)+return-EINVAL;++data->smbus.clk_div=DIV_ROUND_UP(clk_rate,16*frequency);+if(data->smbus.clk_div<4)+returndev_err_probe(data->smbus.dev,-EINVAL,+"Bus frequency %d is too fast.\n",+frequency);+if(data->smbus.clk_div>0xff)+returndev_err_probe(data->smbus.dev,-EINVAL,+"Bus frequency %d is too slow.\n",+frequency);++return0;+}++staticintpasemi_apple_i2c_probe(structplatform_device*pdev)+{+structdevice*dev=&pdev->dev;+structpasemi_apple_i2c_data*data;+structpasemi_smbus*smbus;+u32frequency;+interror;++data=devm_kzalloc(dev,sizeof(structpasemi_apple_i2c_data),+GFP_KERNEL);+if(!data)+return-ENOMEM;++smbus=&data->smbus;+smbus->dev=dev;++smbus->ioaddr=devm_platform_ioremap_resource(pdev,0);+if(IS_ERR(smbus->ioaddr))+returnPTR_ERR(smbus->ioaddr);++if(of_property_read_u32(dev->of_node,"clock-frequency",&frequency))+frequency=I2C_MAX_STANDARD_MODE_FREQ;++data->clk_ref=devm_clk_get(dev,NULL);+if(IS_ERR(data->clk_ref))+returnPTR_ERR(data->clk_ref);++error=clk_prepare_enable(data->clk_ref);+if(error)+returnerror;++error=pasemi_apple_i2c_calc_clk_div(data,frequency);+if(error)+gotoout_clk_disable;++smbus->adapter.dev.of_node=pdev->dev.of_node;+error=pasemi_i2c_common_probe(smbus);+if(error)+gotoout_clk_disable;++platform_set_drvdata(pdev,data);++return0;++out_clk_disable:+clk_disable_unprepare(data->clk_ref);++returnerror;+}++staticintpasemi_apple_i2c_remove(structplatform_device*pdev)+{+structpasemi_apple_i2c_data*data=platform_get_drvdata(pdev);++clk_disable_unprepare(data->clk_ref);+return0;+}++staticconststructof_device_idpasemi_apple_i2c_of_match[]={+{.compatible="apple,t8103-i2c"},+{.compatible="apple,i2c"},+{},+};+MODULE_DEVICE_TABLE(of,pasemi_apple_i2c_of_match);++staticstructplatform_driverpasemi_apple_i2c_driver={+.driver={+.name="i2c-apple",+.of_match_table=pasemi_apple_i2c_of_match,+},+.probe=pasemi_apple_i2c_probe,+.remove=pasemi_apple_i2c_remove,+};+module_platform_driver(pasemi_apple_i2c_driver);++MODULE_LICENSE("GPL");+MODULE_AUTHOR("Sven Peter <sven@svenpeter.dev>");+MODULE_DESCRIPTION("Apple/PASemi SMBus platform driver");
From: Sven Peter <hidden> Date: 2021-09-26 10:00:16
Some later revisions after the original PASemi I2C controller introduce
what likely is an enable bit to the CTL register. Without setting it the
actual i2c transmission is never started.
Signed-off-by: Sven Peter <redacted>
---
drivers/i2c/busses/i2c-pasemi-core.c | 8 ++++++++
drivers/i2c/busses/i2c-pasemi-core.h | 3 +++
drivers/i2c/busses/i2c-pasemi-pci.c | 6 ++++++
3 files changed, 17 insertions(+)
@@ -335,6 +340,9 @@ int pasemi_i2c_common_probe(struct pasemi_smbus *smbus)/* set up the sysfs linkage to our parent device */smbus->adapter.dev.parent=smbus->dev;+if(smbus->hw_rev!=PASEMI_HW_REV_PCI)+smbus->hw_rev=reg_read(smbus,REG_REV);+pasemi_reset(smbus);error=devm_i2c_add_adapter(smbus->dev,&smbus->adapter);
On Sun, Sep 26, 2021 at 12:00 PM Sven Peter [off-list ref] wrote:
In preparation for splitting this driver up into a platform_driver
and a pci_driver, replace outl/inl usage with ioport_map and
ioread32/iowrite32.
Signed-off-by: Sven Peter <redacted>
+ smbus->ioaddr = ioport_map(smbus->base, smbus->size);
+ if (!smbus->ioaddr) {
+ error = -EBUSY;
+ goto out_release_region;
+ }
While this works, I would suggest using the more regular pci_iomap()
or pcim_iomap() helper to turn the port number into an __iomem token.
Arnd
On Sun, Sep 26, 2021 at 12:00 PM Sven Peter [off-list ref] wrote:
This series adds support for the I2C controller found on Apple Silicon Macs
which has quite a bit of history:
Apple bought P.A. Semi in 2008 and it looks like a part of its legacy continues
to live on in the M1. This controller has actually been used since at least the
iPhone 4S and hasn't changed much since then.
Essentially, there are only a few differences that matter:
- The controller no longer is a PCI device
- Starting at some iPhone an additional bit in one register
must be set in order to start transmissions.
- The reference clock and hence the clock dividers are different
In order to add support for a platform device I first replaced PCI-specific
bits and split out the PCI driver to its own file. Then I added support
to make the clock divider configurable and converted the driver to use
managed device resources to make it a bit simpler.
The Apple and PASemi driver will never be compiled in the same kernel
since the Apple one will run on arm64 while the original PASemi driver
will only be useful on powerpc.
I've thus followed the octeon (mips)/thunderx(arm64) approach to do the
split: I created a -core.c file which contains the shared logic and just
compile that one for both the PASemi and the new Apple driver.
This looks all very good to me, I had one very minor comment.
Whole series
Reviewed-by: Arnd Bergmann <arnd@arndb.de>
From: Sven Peter <hidden> Date: 2021-09-28 15:49:10
On Mon, Sep 27, 2021, at 09:39, Arnd Bergmann wrote:
On Sun, Sep 26, 2021 at 12:00 PM Sven Peter [off-list ref] wrote:
quoted
In preparation for splitting this driver up into a platform_driver
and a pci_driver, replace outl/inl usage with ioport_map and
ioread32/iowrite32.
Signed-off-by: Sven Peter <redacted>
+ smbus->ioaddr = ioport_map(smbus->base, smbus->size);
+ if (!smbus->ioaddr) {
+ error = -EBUSY;
+ goto out_release_region;
+ }
While this works, I would suggest using the more regular pci_iomap()
or pcim_iomap() helper to turn the port number into an __iomem token.
Thanks a lot for the review!
I'll replace it with pci_iomap here and then later in this series with
pcim_iomap when also switching the rest to devres.
Thanks,
Sven
From: Rob Herring <robh@kernel.org> Date: 2021-10-04 18:01:44
On Sun, 26 Sep 2021 11:58:38 +0200, Sven Peter wrote:
The Apple I2C controller is based on the PASemi I2C controller.
It is present on Apple SoCs such as the M1.
Signed-off-by: Sven Peter <redacted>
---
.../devicetree/bindings/i2c/apple,i2c.yaml | 61 +++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 62 insertions(+)
create mode 100644 Documentation/devicetree/bindings/i2c/apple,i2c.yaml