diff mbox series

[3/5] i2c: piix4: Add ACPI support for ASF SMBus device

Message ID 20240822142200.686842-4-Shyam-sundar.S-k@amd.com
State New
Headers show
Series Add ASF Controller Support to the i2c-piix4 driver | expand

Commit Message

Shyam Sundar S K Aug. 22, 2024, 2:21 p.m. UTC
The AMD ASF controller is presented to the operating system as an ACPI
device. The piix4 driver can obtain the ASF handle through ACPI to
retrieve information about the ASF controller's attributes, such as the
ASF address space and interrupt number, and to handle ASF interrupts.

Currently, the piix4 driver assumes that a specific port address is
designated for AUX operations. However, with the introduction of ASF, the
same port address may also be used by the ASF controller. Therefore, a
check needs to be added to ensure that if ASF is advertised and enabled in
ACPI, the AUX port is not set up.

Co-developed-by: Sanket Goswami <Sanket.Goswami@amd.com>
Signed-off-by: Sanket Goswami <Sanket.Goswami@amd.com>
Signed-off-by: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
---
 drivers/i2c/busses/i2c-piix4.c | 161 ++++++++++++++++++++++++++++++++-
 1 file changed, 160 insertions(+), 1 deletion(-)

Comments

kernel test robot Aug. 26, 2024, 10:54 a.m. UTC | #1
Hi Shyam,

kernel test robot noticed the following build warnings:

[auto build test WARNING on andi-shyti/i2c/i2c-host]
[also build test WARNING on linus/master v6.11-rc5 next-20240823]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Shyam-Sundar-S-K/i2c-piix4-Allow-more-than-two-algo-selection-for-SMBus/20240826-113028
base:   https://git.kernel.org/pub/scm/linux/kernel/git/andi.shyti/linux.git i2c/i2c-host
patch link:    https://lore.kernel.org/r/20240822142200.686842-4-Shyam-sundar.S-k%40amd.com
patch subject: [PATCH 3/5] i2c: piix4: Add ACPI support for ASF SMBus device
config: i386-buildonly-randconfig-003-20240826 (https://download.01.org/0day-ci/archive/20240826/202408261816.iYeJHXHU-lkp@intel.com/config)
compiler: clang version 18.1.5 (https://github.com/llvm/llvm-project 617a15a9eac96088ae5e9134248d8236e34b91b1)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20240826/202408261816.iYeJHXHU-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202408261816.iYeJHXHU-lkp@intel.com/

All warnings (new ones prefixed by >>):

>> drivers/i2c/busses/i2c-piix4.c:935:6: warning: variable 'cmd' is used uninitialized whenever 'if' condition is false [-Wsometimes-uninitialized]
     935 |         if (!(reg & SB800_ASF_ERROR_STATUS)) {
         |             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   drivers/i2c/busses/i2c-piix4.c:971:8: note: uninitialized use occurs here
     971 |         if (!(cmd & BIT(0))) {
         |               ^~~
   drivers/i2c/busses/i2c-piix4.c:935:2: note: remove the 'if' if its condition is always true
     935 |         if (!(reg & SB800_ASF_ERROR_STATUS)) {
         |         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   drivers/i2c/busses/i2c-piix4.c:927:19: note: initialize the variable 'cmd' to silence this warning
     927 |         u8 bank, reg, cmd;
         |                          ^
         |                           = '\0'
   1 warning generated.


vim +935 drivers/i2c/busses/i2c-piix4.c

   920	
   921	static void sb800_asf_process_slave(struct work_struct *work)
   922	{
   923		struct i2c_piix4_adapdata *adapdata = container_of(work, struct i2c_piix4_adapdata,
   924								   work_buf.work);
   925		unsigned short piix4_smba = adapdata->smba;
   926		u8 data[SB800_ASF_BLOCK_MAX_BYTES];
   927		u8 bank, reg, cmd;
   928		u8 len, val = 0;
   929		int i;
   930	
   931		/* Read slave status register */
   932		reg = inb_p(ASFSLVSTA);
   933	
   934		/* Check if no error bits are set in slave status register */
 > 935		if (!(reg & SB800_ASF_ERROR_STATUS)) {
   936			/* Read data bank */
   937			reg = inb_p(ASFDATABNKSEL);
   938			bank = (reg & BIT(3)) >> 3;
   939	
   940			/* Set read data bank */
   941			if (bank) {
   942				reg = reg | BIT(4);
   943				reg = reg & (~BIT(3));
   944			} else {
   945				reg = reg & (~BIT(4));
   946				reg = reg & (~BIT(2));
   947			}
   948	
   949			/* Read command register */
   950			outb_p(reg, ASFDATABNKSEL);
   951			cmd = inb_p(ASFINDEX);
   952			len = inb_p(ASFDATARWPTR);
   953			for (i = 0; i < len; i++)
   954				data[i] = inb_p(ASFINDEX);
   955	
   956			/* Clear data bank status */
   957			if (bank) {
   958				reg = reg | BIT(3);
   959				outb_p(reg, ASFDATABNKSEL);
   960			} else {
   961				reg = reg | BIT(2);
   962				outb_p(reg, ASFDATABNKSEL);
   963			}
   964		} else {
   965			/* Set bank as full */
   966			reg = reg | (BIT(3) | BIT(2));
   967			outb_p(reg, ASFDATABNKSEL);
   968		}
   969	
   970		outb_p(0, ASFSETDATARDPTR);
   971		if (!(cmd & BIT(0))) {
   972			i2c_slave_event(adapdata->slave, I2C_SLAVE_WRITE_REQUESTED, &val);
   973			for (i = 0; i < len; i++) {
   974				val = data[i];
   975				i2c_slave_event(adapdata->slave, I2C_SLAVE_WRITE_RECEIVED, &val);
   976			}
   977			i2c_slave_event(adapdata->slave, I2C_SLAVE_STOP, &val);
   978		}
   979	}
   980
Shyam Sundar S K Sept. 4, 2024, 11:03 a.m. UTC | #2
On 9/4/2024 01:36, Andy Shevchenko wrote:
> On Wed, Sep 04, 2024 at 01:06:16AM +0530, Shyam Sundar S K wrote:
>> +Andy (this has some ACPI handling that adds AMD ASF support to the
>> existing piix4 driver for SMBus)
> 
> Thanks.
> 
>> On 8/22/2024 19:51, Shyam Sundar S K wrote:
>>> The AMD ASF controller is presented to the operating system as an ACPI
>>> device. The piix4 driver can obtain the ASF handle through ACPI to
>>> retrieve information about the ASF controller's attributes, such as the
>>> ASF address space and interrupt number, and to handle ASF interrupts.
>>>
>>> Currently, the piix4 driver assumes that a specific port address is
>>> designated for AUX operations. However, with the introduction of ASF, the
>>> same port address may also be used by the ASF controller. Therefore, a
>>> check needs to be added to ensure that if ASF is advertised and enabled in
>>> ACPI, the AUX port is not set up.
> 
> ...
> 
>>> +static acpi_status sb800_asf_acpi_resource_cb(struct acpi_resource *resource, void *context)
>>> +{
>>> +	struct sb800_asf_data *data = context;
>>> +
>>> +	switch (resource->type) {
>>> +	case ACPI_RESOURCE_TYPE_EXTENDED_IRQ:
>>> +		data->irq = resource->data.extended_irq.interrupts[0];
>>> +		break;
>>> +	case ACPI_RESOURCE_TYPE_IO:
>>> +		data->addr = resource->data.io.minimum;
>>> +		break;
>>> +	}
>>> +
>>> +	return AE_OK;
>>> +}
>>> +
>>> +static int sb800_asf_add_adap(struct pci_dev *dev)
>>> +{
>>> +	struct i2c_piix4_adapdata *adapdata;
>>> +	struct sb800_asf_data *data;
>>> +	acpi_status status;
>>> +	acpi_handle handle;
>>> +	int ret;
> 
>>> +	status = acpi_get_handle(NULL, SB800_ASF_ACPI_PATH, &handle);
>>> +	if (ACPI_FAILURE(status))
>>> +		return -ENODEV;
> 
>>> +	data = devm_kzalloc(&dev->dev, sizeof(struct sb800_asf_data), GFP_KERNEL);
>>> +	if (!data)
>>> +		return -ENOMEM;
> 
> Why can't it be on stack?
> 
>>> +	status = acpi_walk_resources(handle, METHOD_NAME__CRS, sb800_asf_acpi_resource_cb, data);
>>> +	if (ACPI_FAILURE(status))
>>> +		return -EINVAL;
>>> +
>>> +	if (!data->addr)
>>> +		return -EINVAL;
> 
> This is reinvention of acpi_dev_get_resources(). Many drivers are using it, you
> may found a lot of examples.

Thank you for the quick feedback. I have submitted v2 addressing your
remarks. Kindly take a look.

Thanks,
Shyam
diff mbox series

Patch

diff --git a/drivers/i2c/busses/i2c-piix4.c b/drivers/i2c/busses/i2c-piix4.c
index a44b53dd4dd7..00fc641e6277 100644
--- a/drivers/i2c/busses/i2c-piix4.c
+++ b/drivers/i2c/busses/i2c-piix4.c
@@ -121,6 +121,8 @@ 
 #define SB800_PIIX4_FCH_PM_ADDR			0xFED80300
 #define SB800_PIIX4_FCH_PM_SIZE			8
 #define SB800_ASF_BLOCK_MAX_BYTES		72
+#define SB800_ASF_ERROR_STATUS			0xE
+#define SB800_ASF_ACPI_PATH			"\\_SB.ASFC"
 
 /* insmod parameters */
 
@@ -185,6 +187,11 @@  struct sb800_mmio_cfg {
 	bool use_mmio;
 };
 
+struct sb800_asf_data {
+	unsigned short addr;
+	int irq;
+};
+
 enum piix4_algo {
 	SMBUS_SB800,
 	SMBUS_LEGACY,
@@ -201,6 +208,8 @@  struct i2c_piix4_adapdata {
 	struct sb800_mmio_cfg mmio_cfg;
 	u8 algo_select;
 	struct i2c_client *slave;
+	bool is_asf;
+	struct delayed_work work_buf;
 };
 
 static int piix4_sb800_region_request(struct device *dev,
@@ -909,6 +918,66 @@  static s32 piix4_access_sb800(struct i2c_adapter *adap, u16 addr,
 	return retval;
 }
 
+static void sb800_asf_process_slave(struct work_struct *work)
+{
+	struct i2c_piix4_adapdata *adapdata = container_of(work, struct i2c_piix4_adapdata,
+							   work_buf.work);
+	unsigned short piix4_smba = adapdata->smba;
+	u8 data[SB800_ASF_BLOCK_MAX_BYTES];
+	u8 bank, reg, cmd;
+	u8 len, val = 0;
+	int i;
+
+	/* Read slave status register */
+	reg = inb_p(ASFSLVSTA);
+
+	/* Check if no error bits are set in slave status register */
+	if (!(reg & SB800_ASF_ERROR_STATUS)) {
+		/* Read data bank */
+		reg = inb_p(ASFDATABNKSEL);
+		bank = (reg & BIT(3)) >> 3;
+
+		/* Set read data bank */
+		if (bank) {
+			reg = reg | BIT(4);
+			reg = reg & (~BIT(3));
+		} else {
+			reg = reg & (~BIT(4));
+			reg = reg & (~BIT(2));
+		}
+
+		/* Read command register */
+		outb_p(reg, ASFDATABNKSEL);
+		cmd = inb_p(ASFINDEX);
+		len = inb_p(ASFDATARWPTR);
+		for (i = 0; i < len; i++)
+			data[i] = inb_p(ASFINDEX);
+
+		/* Clear data bank status */
+		if (bank) {
+			reg = reg | BIT(3);
+			outb_p(reg, ASFDATABNKSEL);
+		} else {
+			reg = reg | BIT(2);
+			outb_p(reg, ASFDATABNKSEL);
+		}
+	} else {
+		/* Set bank as full */
+		reg = reg | (BIT(3) | BIT(2));
+		outb_p(reg, ASFDATABNKSEL);
+	}
+
+	outb_p(0, ASFSETDATARDPTR);
+	if (!(cmd & BIT(0))) {
+		i2c_slave_event(adapdata->slave, I2C_SLAVE_WRITE_REQUESTED, &val);
+		for (i = 0; i < len; i++) {
+			val = data[i];
+			i2c_slave_event(adapdata->slave, I2C_SLAVE_WRITE_RECEIVED, &val);
+		}
+		i2c_slave_event(adapdata->slave, I2C_SLAVE_STOP, &val);
+	}
+}
+
 static void sb800_asf_update_bits(unsigned short piix4_smba, u8 bit, unsigned long offset, bool set)
 {
 	unsigned long reg;
@@ -1195,6 +1264,86 @@  static int piix4_add_adapter(struct pci_dev *dev, unsigned short smba,
 	return 0;
 }
 
+static irqreturn_t sb800_asf_irq_handler(int irq, void *ptr)
+{
+	struct i2c_piix4_adapdata *adapdata = (struct i2c_piix4_adapdata *)ptr;
+	unsigned short piix4_smba = adapdata->smba;
+	u8 slave_int = inb_p(ASFSTA);
+
+	if ((slave_int & BIT(6))) {
+		/* Slave Interrupt */
+		outb_p(slave_int | BIT(6), ASFSTA);
+		schedule_delayed_work(&adapdata->work_buf, HZ);
+	} else {
+		/* Master Interrupt */
+		sb800_asf_update_bits(piix4_smba, SB800_ASF_SLV_INTR, SMBHSTSTS, true);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static acpi_status sb800_asf_acpi_resource_cb(struct acpi_resource *resource, void *context)
+{
+	struct sb800_asf_data *data = context;
+
+	switch (resource->type) {
+	case ACPI_RESOURCE_TYPE_EXTENDED_IRQ:
+		data->irq = resource->data.extended_irq.interrupts[0];
+		break;
+	case ACPI_RESOURCE_TYPE_IO:
+		data->addr = resource->data.io.minimum;
+		break;
+	}
+
+	return AE_OK;
+}
+
+static int sb800_asf_add_adap(struct pci_dev *dev)
+{
+	struct i2c_piix4_adapdata *adapdata;
+	struct sb800_asf_data *data;
+	acpi_status status;
+	acpi_handle handle;
+	int ret;
+
+	status = acpi_get_handle(NULL, SB800_ASF_ACPI_PATH, &handle);
+	if (ACPI_FAILURE(status))
+		return -ENODEV;
+
+	data = devm_kzalloc(&dev->dev, sizeof(struct sb800_asf_data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	status = acpi_walk_resources(handle, METHOD_NAME__CRS, sb800_asf_acpi_resource_cb, data);
+	if (ACPI_FAILURE(status))
+		return -EINVAL;
+
+	if (!data->addr)
+		return -EINVAL;
+
+	ret = piix4_add_adapter(dev, data->addr, SMBUS_ASF, piix4_adapter_count, false, 0,
+				piix4_main_port_names_sb800[piix4_adapter_count],
+				&piix4_main_adapters[piix4_adapter_count]);
+	if (ret) {
+		dev_err(&dev->dev, "Failed to add ASF adapter: %d\n", ret);
+		return -ENODEV;
+	}
+
+	adapdata = i2c_get_adapdata(piix4_main_adapters[piix4_adapter_count]);
+	ret = devm_request_irq(&dev->dev, data->irq, sb800_asf_irq_handler, IRQF_SHARED,
+			       "sb800_smbus_asf", adapdata);
+	if (ret) {
+		dev_err(&dev->dev, "Unable to request irq: %d for use\n", data->irq);
+		return ret;
+	}
+
+	INIT_DELAYED_WORK(&adapdata->work_buf, sb800_asf_process_slave);
+	adapdata->is_asf = true;
+	/* Increment the adapter count by 1 as ASF is added to the list */
+	piix4_adapter_count += 1;
+	return 1;
+}
+
 static int piix4_add_adapters_sb800(struct pci_dev *dev, unsigned short smba,
 				    bool notify_imc)
 {
@@ -1243,6 +1392,7 @@  static int piix4_probe(struct pci_dev *dev, const struct pci_device_id *id)
 {
 	int retval;
 	bool is_sb800 = false;
+	bool is_asf = false;
 
 	if ((dev->vendor == PCI_VENDOR_ID_ATI &&
 	     dev->device == PCI_DEVICE_ID_ATI_SBX00_SMBUS &&
@@ -1279,6 +1429,10 @@  static int piix4_probe(struct pci_dev *dev, const struct pci_device_id *id)
 		retval = piix4_add_adapters_sb800(dev, retval, notify_imc);
 		if (retval < 0)
 			return retval;
+
+		/* Check if ASF is enabled in SB800 */
+		if (sb800_asf_add_adap(dev))
+			is_asf = true;
 	} else {
 		retval = piix4_setup(dev, id);
 		if (retval < 0)
@@ -1308,7 +1462,9 @@  static int piix4_probe(struct pci_dev *dev, const struct pci_device_id *id)
 	if (dev->vendor == PCI_VENDOR_ID_AMD &&
 	    (dev->device == PCI_DEVICE_ID_AMD_HUDSON2_SMBUS ||
 	     dev->device == PCI_DEVICE_ID_AMD_KERNCZ_SMBUS)) {
-		retval = piix4_setup_sb800(dev, id, 1);
+		/* Do not setup AUX port if ASF is enabled */
+		if (!is_asf)
+			retval = piix4_setup_sb800(dev, id, 1);
 	}
 
 	if (retval > 0) {
@@ -1326,6 +1482,9 @@  static void piix4_adap_remove(struct i2c_adapter *adap)
 {
 	struct i2c_piix4_adapdata *adapdata = i2c_get_adapdata(adap);
 
+	if (adapdata->is_asf)
+		cancel_delayed_work_sync(&adapdata->work_buf);
+
 	if (adapdata->smba) {
 		i2c_del_adapter(adap);
 		if (adapdata->port == (0 << piix4_port_shift_sb800))