* [PATCH] i2c: sis964: bus driver
@ 2012-07-13 9:40 Amaury Decrême
2012-07-13 15:36 ` Bjorn Helgaas
0 siblings, 1 reply; 5+ messages in thread
From: Amaury Decrême @ 2012-07-13 9:40 UTC (permalink / raw)
To: khali, linux-i2c
Cc: amalysh, nelson, ben-linux, w.sang, rob, bhelgaas,
jeffrey.t.kirsher, akpm, davem, joe, ralf, dirk.brandewie,
jayachandranc, Xiangzhen.Ye, linux-doc, linux-kernel, linux-pci,
suzanne.decreme, Amaury Decrême
This patch is a driver for SiS964 I2C bus.
It was forked from i2c-sis630 and modified with SiS datasheets.
Tested with kmemleak.
Signed-off-by: Amaury Decrême <amaury.decreme@gmail.com>
---
Documentation/i2c/busses/i2c-sis964 | 34 ++
MAINTAINERS | 16 +
drivers/i2c/busses/Kconfig | 12 +-
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-sis964.c | 575 +++++++++++++++++++++++++++++++++++
include/linux/pci_ids.h | 1 +
6 files changed, 638 insertions(+), 1 deletions(-)
create mode 100644 Documentation/i2c/busses/i2c-sis964
create mode 100644 drivers/i2c/busses/i2c-sis964.c
diff --git a/Documentation/i2c/busses/i2c-sis964 b/Documentation/i2c/busses/i2c-sis964
new file mode 100644
index 0000000..a831f1a
--- /dev/null
+++ b/Documentation/i2c/busses/i2c-sis964
@@ -0,0 +1,34 @@
+Kernel driver i2c-sis964
+
+Supported adapters:
+ * Silicon Integrated Systems Corp (SiS)
+ 964 chipset (Datasheet by SiS)
+ * Possible other SiS chipsets with the same registers and clocks
+
+Author: Amaury Decrême <amaury.decreme@gmail.com>
+
+Module Parameters
+-----------------
+
+* force = [1|0] Forcibly enable the SIS964. DANGEROUS!
+ This can be interesting for chipsets not named
+ above to check if it works for you chipset,
+ but DANGEROUS!
+
+* low_clock = [1|0] 1 = Set Host Master Clock to 28KHz (defaut 56Khz)
+
+Description
+-----------
+
+This SMBus driver is known to work on motherboards with the SiS964 chipset.
+
+If you see something like this:
+
+00:02.0 ISA bridge: Silicon Integrated Systems [SiS] SiS964 [MuTIOL Media IO]
+
+in your 'lspci' output , then this driver is for your chipset.
+
+Thank You
+---------
+Alexander Malysh <amalysh@web.de>
+- Who has written i2c-sis630, from which i2c-sis964 is forked
diff --git a/MAINTAINERS b/MAINTAINERS
index eb22272..4a11805 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -6179,6 +6179,22 @@ S: Maintained
F: Documentation/i2c/busses/i2c-sis96x
F: drivers/i2c/busses/i2c-sis96x.c
+SIS 964 I2C/SMBUS DRIVER
+M: "Amaury Decrême" <amaury.decreme@gmail.com>
+L: linux-i2c@vger.kernel.org
+S: Maintained
+F: Documentation/i2c/busses/i2c-sis96i4
+F: drivers/i2c/busses/i2c-sis964.c
+
+SIS FRAMEBUFFER DRIVER
+M: Thomas Winischhofer <thomas@winischhofer.net>
+W: http://www.winischhofer.net/linuxsisvga.shtml
+S: Maintained
+F: Documentation/fb/sisfb.txt
+F: drivers/video/sis/
+F: include/video/sisfb.h
+
+SIS USB2VGA DRIVER
SIS FRAMEBUFFER DRIVER
M: Thomas Winischhofer <thomas@winischhofer.net>
W: http://www.winischhofer.net/linuxsisvga.shtml
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 7244c8b..8dc9f90 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -189,8 +189,18 @@ config I2C_SIS630
This driver can also be built as a module. If so, the module
will be called i2c-sis630.
+config I2C_SIS964
+ tristate "SiS 964"
+ depends on PCI && EXPERIMENTAL
+ help
+ If you say yes to this option, support will be included for the SiS
+ 964 SMBus (a subset of I2C) interfaces.
+
+ This driver can also be built as a module. If so, the module
+ will be called i2c-sis964.
+
config I2C_SIS96X
- tristate "SiS 96x"
+ tristate "SiS 96x (but SiS964)"
depends on PCI
help
If you say yes to this option, support will be included for the SiS
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index ce3c2be..b985bc8 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -19,6 +19,7 @@ obj-$(CONFIG_I2C_NFORCE2_S4985) += i2c-nforce2-s4985.o
obj-$(CONFIG_I2C_PIIX4) += i2c-piix4.o
obj-$(CONFIG_I2C_SIS5595) += i2c-sis5595.o
obj-$(CONFIG_I2C_SIS630) += i2c-sis630.o
+obj-$(CONFIG_I2C_SIS964) += i2c-sis964.o
obj-$(CONFIG_I2C_SIS96X) += i2c-sis96x.o
obj-$(CONFIG_I2C_VIA) += i2c-via.o
obj-$(CONFIG_I2C_VIAPRO) += i2c-viapro.o
diff --git a/drivers/i2c/busses/i2c-sis964.c b/drivers/i2c/busses/i2c-sis964.c
new file mode 100644
index 0000000..9f4ed14
--- /dev/null
+++ b/drivers/i2c/busses/i2c-sis964.c
@@ -0,0 +1,575 @@
+/*
+ Copyright (c) 2012 Amaury Decrême <amaury.decreme@gmail.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+/*
+ Changes:
+ 11.08.2011
+ Fork of original i2c-sis630 - Alexander Malysh <amalysh@web.de>
+ Adapted for SiS964 with datasheets
+ - Amaury Decrême <amaury.decreme@gmail.com>
+*/
+
+/*
+ Supports:
+ SIS 964
+
+ Note: we assume there can only be one device, with one SMBus interface.
+*/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/pci.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/acpi.h>
+#include <linux/io.h>
+
+/* SIS964 SMBus registers */
+#define SMB_STS 0xE0 /* status */
+#define SMB_EN 0xE1 /* status enable */
+#define SMB_CNT 0xE2 /* Control */
+#define SMBHOST_CNT 0xE3 /* Host Control */
+#define SMB_ADDR 0xE4 /* Address */
+#define SMB_CMD 0xE5 /* Command */
+#define SMB_PERRCHK 0xE6 /* Packet Error Check */
+#define SMB_COUNT 0xE7 /* Byte Count */
+#define SMB_BYTE 0xE8 /* ~0x8F data byte field */
+#define SMBDEV_ADDR 0xF0 /* Device Address */
+#define SMB_DB0 0xF1 /* Device byte0 */
+#define SMB_DB1 0xF2 /* Device byte1 */
+#define SMB_SAA 0xF3 /* Host slave alias address */
+#define SMB_PCOUNT 0xF4 /* processed byte count */
+
+
+/* SMB_STS register */
+#define SMBALT_STS 0x80 /* Slave alert */
+#define BYTE_DONE_STS 0x10 /* Byte Done Status / Block Array */
+#define SMBMAS_STS 0x08 /* Host Master */
+#define SMBCOL_STS 0x04 /* Collision */
+#define SMBERR_STS 0x02 /* Device error */
+
+/* SMB_CNT register */
+#define SMBCLK_SEL 0x20 /* Host master clock selection */
+#define SMB_PROBE 0x02 /* Bus Probe */
+#define SMB_HOSTBUSY 0x01 /* Host Busy */
+
+/* SMBHOST_CNT register */
+#define SMB_KILL 0x20 /* Kill */
+#define SMB_START 0x10 /* Start */
+#define SMB_PTL 0x07 /* Command Protocol */
+
+
+/* SMB_ADDR register */
+#define SMB_ADDRESS 0xFE /* Adress */
+#define SMB_RW 0x01 /* Read/Write */
+
+
+/* SMB_BYTE register */
+#define SMB_BYTE0 0xFF /* Byte 0 */
+#define SMB_BYTE1 0xFF00 /* Byte 1 */
+
+/* register count for request_region */
+#define SIS964_SMB_IOREGION 21
+
+/* PCI address constants */
+/* acpi base address register */
+#define SIS964_ACPI_BASE_REG 0x74
+/* bios control register */
+#define SIS964_BIOS_CTL_REG 0x40
+
+/* Other settings */
+#define MAX_TIMEOUT 500
+
+/* SIS964 constants */
+#define SIS964_QUICK 0x00
+#define SIS964_BYTE 0x01
+#define SIS964_BYTE_DATA 0x02
+#define SIS964_WORD_DATA 0x03
+#define SIS964_PCALL 0x04
+#define SIS964_BLOCK_DATA 0x05
+
+static struct pci_driver sis964_driver;
+
+/* insmod parameters */
+static bool low_clock;
+static bool force;
+module_param(low_clock, bool, 0);
+MODULE_PARM_DESC(low_clock, "Set Host Master Clock to 28KHz (default 56KHz).");
+module_param(force, bool, 0);
+MODULE_PARM_DESC(force, "Forcibly enable the SIS964. DANGEROUS!");
+
+/* acpi base address */
+static unsigned short acpi_base;
+
+/* supported chips */
+static int supported[] = {
+ PCI_DEVICE_ID_SI_964,
+ 0 /* terminates the list */
+};
+
+static inline u8 sis964_read(u8 reg)
+{
+ return inb(acpi_base + reg);
+}
+
+static inline void sis964_write(u8 reg, u8 data)
+{
+ outb(data, acpi_base + reg);
+}
+
+static int sis964_transaction_start(struct i2c_adapter *adap,
+ int ptl, u8 *oldclock)
+{
+ int tmp = 0;
+
+ /* Clear status register */
+ sis964_write(SMB_STS, 0xFF);
+
+ /* Make sure the SMBus host is ready to start transmitting. */
+ tmp = sis964_read(SMB_CNT);
+ if (tmp & (SMB_PROBE | SMB_HOSTBUSY)) {
+ dev_dbg(&adap->dev,
+ "Bus busy (status 0x%02x). Killing transaction.\n",
+ tmp);
+
+ sis964_write(SMBHOST_CNT, SMB_KILL);
+
+ return -EBUSY;
+ }
+
+ /* Set Host Master Clock to 28KHz if requested */
+ if (low_clock) {
+ *oldclock = sis964_read(SMB_CNT);
+ sis964_write(SMB_CNT, SMBCLK_SEL);
+ }
+
+ /* start the transaction by setting bit start and protocol */
+ sis964_write(SMBHOST_CNT, SMB_START | (ptl & SMB_PTL));
+
+ return 0;
+}
+
+static int sis964_transaction_wait(struct i2c_adapter *adap, int ptl)
+{
+ int tmp = 0, timeout = 0;
+
+ /* Wait 30us, valid for 28Khz and 56Khz */
+ udelay(30);
+
+ tmp = sis964_read(SMB_STS);
+ if (!(tmp & SMB_PROBE) && (tmp & SMB_HOSTBUSY)) {
+ dev_dbg(&adap->dev,
+ "Host busy (status 0x%02x). Restarting transaction.\n",
+ tmp);
+ sis964_write(SMBHOST_CNT, SMB_KILL);
+ return -EAGAIN;
+ }
+
+ while (!(ptl == SIS964_BLOCK_DATA && (tmp & BYTE_DONE_STS))
+ && !(tmp & (SMBMAS_STS | SMBCOL_STS | SMBERR_STS))
+ && (timeout++ < MAX_TIMEOUT)) {
+
+ /* Datasheets: wait 4ms max at 28Khz and
+ * 2ms max at 56Khz for 8 bytes */
+ if (low_clock)
+ udelay(4000);
+ else
+ udelay(2000);
+ tmp = sis964_read(SMB_STS);
+ }
+
+ /* If the SMBus is still busy, we give up */
+ if (timeout > MAX_TIMEOUT) {
+ dev_dbg(&adap->dev,
+ "Bus Timeout (status 0x%02x)!\n", tmp);
+ return -ETIMEDOUT;
+ }
+
+ if (tmp & SMBERR_STS) {
+ dev_dbg(&adap->dev,
+ "Failed bus transaction (status 0x%02x)!\n", tmp);
+ return -ENXIO;
+ }
+
+ if (tmp & SMBCOL_STS) {
+ dev_err(&adap->dev,
+ "Bus collision (status 0x%02x)!\n", tmp);
+ sis964_write(SMB_STS, tmp & ~SMBCOL_STS);
+ return -EAGAIN;
+ }
+
+ return 0;
+}
+
+static void sis964_transaction_end(u8 oldclock)
+{
+ /* clear all status "sticky" bits */
+ sis964_write(SMB_STS, 0xFF);
+
+ /* restore old Host Master Clock if low_clock is set */
+ if (low_clock)
+ sis964_write(SMB_CNT, oldclock & SMBCLK_SEL);
+}
+
+static int sis964_transaction(struct i2c_adapter *adap, int ptl)
+{
+ int tmp = 0, timeout = 0;
+ u8 oldclock = 0;
+
+ do {
+ tmp = sis964_transaction_start(adap, ptl, &oldclock);
+ if (tmp)
+ return tmp;
+
+ tmp = sis964_transaction_wait(adap, ptl);
+ sis964_transaction_end(oldclock);
+ } while (tmp == -EAGAIN && timeout++ < MAX_TIMEOUT);
+
+ if (timeout > MAX_TIMEOUT) {
+ dev_dbg(&adap->dev, "Bus timeout !\n");
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static int sis964_block_data_read(struct i2c_adapter *adap,
+ union i2c_smbus_data *data)
+{
+ int i, len = 0, tmp = 0;
+ u8 oldclock = 0;
+
+ data->block[0] = len = 0;
+
+ tmp = sis964_transaction_start(adap, SIS964_BLOCK_DATA, &oldclock);
+ if (tmp)
+ return tmp;
+
+ do {
+ tmp = sis964_transaction_wait(adap, SIS964_BLOCK_DATA);
+ if (tmp) {
+ dev_dbg(&adap->dev, "Transaction wait failed\n");
+ break;
+ }
+
+ /* if this first transaction then read byte count */
+ if (len == 0)
+ data->block[0] = sis964_read(SMB_COUNT);
+
+ if (data->block[0] > 32)
+ data->block[0] = 32;
+
+ dev_dbg(&adap->dev, "Block data read len=0x%x\n",
+ data->block[0]);
+
+ for (i = 0; i < 8 && len < data->block[0]; i++, len++) {
+ dev_dbg(&adap->dev, "Read i=%d len=%d\n", i, len);
+ data->block[len+1] = sis964_read(SMB_BYTE+i);
+ }
+
+ /* clear BYTE_DONE_STS */
+ sis964_write(SMB_STS, BYTE_DONE_STS);
+ } while (len < data->block[0]);
+
+ sis964_transaction_end(oldclock);
+
+ return 0;
+}
+
+
+static int sis964_block_data_write(struct i2c_adapter *adap,
+ union i2c_smbus_data *data)
+{
+
+ int i, len = 0, tmp = 0;
+ u8 oldclock = 0;
+
+ len = data->block[0];
+ if (len < 0)
+ len = 0;
+ else if (len > 32)
+ len = 32;
+
+ sis964_write(SMB_COUNT, len);
+
+ for (i = 1; i <= len; i++) {
+ dev_dbg(&adap->dev, "Set data 0x%02x\n", data->block[i]);
+
+ /* set data */
+ sis964_write(SMB_BYTE+(i-1)%8, data->block[i]);
+ if (i == 8 || (len < 8 && i == len)) {
+
+ /* first transaction */
+ tmp = sis964_transaction_start(adap, SIS964_BLOCK_DATA,
+ &oldclock);
+ if (tmp)
+ return tmp;
+
+ } else if ((i-1)%8 == 7 || i == len) {
+ if (i > 8) {
+ dev_dbg(&adap->dev,
+ "Clear smbary_sts len=%d i=%d\n", len, i);
+
+ /*
+ If this is not first transaction,
+ we must clear sticky bit.
+ clear BYTE_DONE-STS
+ */
+ sis964_write(SMB_STS, BYTE_DONE_STS);
+ }
+ tmp = sis964_transaction_wait(adap,
+ SIS964_BLOCK_DATA);
+ if (tmp) {
+ dev_dbg(&adap->dev,
+ "Transaction wait failed\n");
+ break;
+ }
+ }
+ }
+
+ sis964_transaction_end(oldclock);
+
+ return 0;
+}
+
+static int sis964_block_data(struct i2c_adapter *adap,
+ union i2c_smbus_data *data, int read_write)
+{
+ if (read_write == I2C_SMBUS_WRITE)
+ return sis964_block_data_write(adap, data);
+ else
+ return sis964_block_data_read(adap, data);
+}
+
+/* Return negative errno on error. */
+static s32 sis964_access(struct i2c_adapter *adap, u16 addr,
+ unsigned short flags, char read_write,
+ u8 command, int ptl, union i2c_smbus_data *data)
+{
+ int tmp = 0;
+
+ switch (ptl) {
+ case I2C_SMBUS_QUICK:
+ sis964_write(SMB_ADDR, ((addr << 1) & SMB_ADDRESS) |
+ (read_write & SMB_RW));
+ ptl = SIS964_QUICK;
+ break;
+ case I2C_SMBUS_BYTE:
+ sis964_write(SMB_ADDR, ((addr << 1) & SMB_ADDRESS) |
+ (read_write & SMB_RW));
+ if (read_write == I2C_SMBUS_WRITE)
+ sis964_write(SMB_CMD, command);
+ ptl = SIS964_BYTE;
+ break;
+ case I2C_SMBUS_BYTE_DATA:
+ sis964_write(SMB_ADDR, ((addr << 1) & SMB_ADDRESS) |
+ (read_write & SMB_RW));
+ sis964_write(SMB_CMD, command);
+ if (read_write == I2C_SMBUS_WRITE)
+ sis964_write(SMB_BYTE, data->byte);
+ ptl = SIS964_BYTE_DATA;
+ break;
+ case I2C_SMBUS_PROC_CALL:
+ case I2C_SMBUS_WORD_DATA:
+ sis964_write(SMB_ADDR, ((addr << 1) & SMB_ADDRESS) |
+ (read_write & SMB_RW));
+ sis964_write(SMB_CMD, command);
+ if (read_write == I2C_SMBUS_WRITE) {
+ sis964_write(SMB_BYTE, data->word & SMB_BYTE0);
+ sis964_write(SMB_BYTE + 1,
+ (data->word & SMB_BYTE1) >> 8);
+ }
+ ptl = (ptl == I2C_SMBUS_PROC_CALL ?
+ SIS964_PCALL : SIS964_WORD_DATA);
+ break;
+ case I2C_SMBUS_BLOCK_DATA:
+ sis964_write(SMB_ADDR, ((addr << 1) & SMB_ADDRESS) |
+ (read_write & SMB_RW));
+ sis964_write(SMB_CMD, command);
+ ptl = SIS964_BLOCK_DATA;
+ return sis964_block_data(adap, data, read_write);
+ default:
+ dev_warn(&adap->dev, "Unsupported transaction %d\n",
+ ptl);
+ return -EOPNOTSUPP;
+ }
+
+ tmp = sis964_transaction(adap, ptl);
+ if (tmp)
+ return tmp;
+
+ if (ptl != SIS964_PCALL &&
+ (read_write == I2C_SMBUS_WRITE || ptl == SIS964_QUICK)) {
+ return 0;
+ }
+
+ switch (ptl) {
+ case SIS964_BYTE:
+ case SIS964_BYTE_DATA:
+ data->byte = sis964_read(SMB_BYTE);
+ break;
+ case SIS964_PCALL:
+ case SIS964_WORD_DATA:
+ data->word = sis964_read(SMB_BYTE) +
+ (sis964_read(SMB_BYTE + 1) << 8);
+ break;
+ }
+
+ return 0;
+}
+
+static u32 sis964_func(struct i2c_adapter *adapter)
+{
+ /* SMBus Command protocol supported */
+ return I2C_FUNC_SMBUS_QUICK | /* Quick command */
+ I2C_FUNC_SMBUS_BYTE | /* Send/Receive Byte */
+ I2C_FUNC_SMBUS_BYTE_DATA | /* Read/Write Byte Data */
+ I2C_FUNC_SMBUS_WORD_DATA | /* Read/Write Word Data */
+ I2C_FUNC_SMBUS_PROC_CALL | /* Process Call */
+ I2C_FUNC_SMBUS_BLOCK_DATA; /* Read/Write Block Data */
+}
+
+static int __devinit sis964_setup(struct pci_dev *sis964_dev)
+{
+ unsigned char b;
+ struct pci_dev *dummy = NULL;
+ int tmp = 0, i;
+
+ /* check for supported SiS devices */
+ for (i = 0; supported[i] > 0 && dummy == NULL; i++)
+ dummy = pci_get_device(PCI_VENDOR_ID_SI, supported[i], dummy);
+
+ if (dummy) {
+ pci_dev_put(dummy);
+ } else if (force) {
+ dev_err(&sis964_dev->dev,
+ "WARNING: Can't detect SIS964 compatible device, but "
+ "loading because of force option enabled\n");
+ } else {
+ return -ENODEV;
+ }
+
+
+ /*
+ Enable ACPI first , so we can accsess reg 74-75
+ in acpi io space and read acpi base addr
+ */
+ if (pci_read_config_byte(sis964_dev, SIS964_BIOS_CTL_REG, &b)) {
+ dev_err(&sis964_dev->dev, "Error: Can't read bios ctl reg\n");
+ return -ENODEV;
+ }
+ /* if ACPI already enabled , do nothing */
+ if (!(b & 0x80) &&
+ pci_write_config_byte(sis964_dev, SIS964_BIOS_CTL_REG, b | 0x80)) {
+ dev_err(&sis964_dev->dev, "Error: Can't enable ACPI\n");
+ return -ENODEV;
+ }
+
+ /* Determine the ACPI base address */
+ if (pci_read_config_word(sis964_dev, SIS964_ACPI_BASE_REG,
+ &acpi_base)) {
+ dev_err(&sis964_dev->dev,
+ "Error: Can't determine ACPI base address\n");
+ return -ENODEV;
+ }
+
+ dev_dbg(&sis964_dev->dev, "ACPI base at 0x%04x\n", acpi_base);
+
+ tmp = acpi_check_region(acpi_base + SMB_STS, SIS964_SMB_IOREGION,
+ sis964_driver.name);
+ if (tmp) {
+ acpi_base = 0;
+ return -ENODEV;
+ }
+
+ /* Everything is happy, let's grab the memory and set things up. */
+ if (!request_region(acpi_base + SMB_STS, SIS964_SMB_IOREGION,
+ sis964_driver.name)) {
+ dev_err(&sis964_dev->dev,
+ "SMBus registers 0x%04x-0x%04x already in use!\n",
+ acpi_base + SMB_STS, acpi_base + SMB_SAA);
+ acpi_base = 0;
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+
+static const struct i2c_algorithm smbus_algorithm = {
+ .smbus_xfer = sis964_access,
+ .functionality = sis964_func,
+};
+
+static struct i2c_adapter sis964_adapter = {
+ .owner = THIS_MODULE,
+ .class = I2C_CLASS_HWMON | I2C_CLASS_SPD,
+ .algo = &smbus_algorithm,
+};
+
+static DEFINE_PCI_DEVICE_TABLE(sis964_ids) = {
+ { PCI_DEVICE(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_964) },
+ { 0 }
+};
+
+MODULE_DEVICE_TABLE(pci, sis964_ids);
+
+static int __devinit sis964_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ if (sis964_setup(dev)) {
+ dev_err(&dev->dev,
+ "SIS964 comp. bus not detected, module not inserted.\n");
+ return -ENODEV;
+ }
+
+ /* set up the sysfs linkage to our parent device */
+ sis964_adapter.dev.parent = &dev->dev;
+
+ snprintf(sis964_adapter.name, sizeof(sis964_adapter.name),
+ "SMBus SIS964 adapter at %04xh", acpi_base + SMB_STS);
+
+ return i2c_add_adapter(&sis964_adapter);
+}
+
+static void __devexit sis964_remove(struct pci_dev *dev)
+{
+ dev_dbg(&dev->dev, "sis964_remove");
+
+ if (acpi_base) {
+ i2c_del_adapter(&sis964_adapter);
+ release_region(acpi_base + SMB_STS, SIS964_SMB_IOREGION);
+ acpi_base = 0;
+ }
+}
+
+
+static struct pci_driver sis964_driver = {
+ .name = "sis964_smbus",
+ .id_table = sis964_ids,
+ .probe = sis964_probe,
+ .remove = __devexit_p(sis964_remove),
+};
+
+module_pci_driver(sis964_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Amaury Decrême <amaury.decreme@gmail.com>");
+MODULE_DESCRIPTION("SiS964 SMBus driver");
diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h
index ab741b0..0ffc982 100644
--- a/include/linux/pci_ids.h
+++ b/include/linux/pci_ids.h
@@ -699,6 +699,7 @@
#define PCI_DEVICE_ID_SI_961 0x0961
#define PCI_DEVICE_ID_SI_962 0x0962
#define PCI_DEVICE_ID_SI_963 0x0963
+#define PCI_DEVICE_ID_SI_964 0x0964
#define PCI_DEVICE_ID_SI_965 0x0965
#define PCI_DEVICE_ID_SI_966 0x0966
#define PCI_DEVICE_ID_SI_968 0x0968
--
1.7.8.6
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH] i2c: sis964: bus driver
2012-07-13 9:40 [PATCH] i2c: sis964: bus driver Amaury Decrême
@ 2012-07-13 15:36 ` Bjorn Helgaas
2012-07-14 7:53 ` Amaury Decrême
2012-07-15 11:35 ` Jean Delvare
0 siblings, 2 replies; 5+ messages in thread
From: Bjorn Helgaas @ 2012-07-13 15:36 UTC (permalink / raw)
To: Amaury Decrême
Cc: khali, linux-i2c, amalysh, nelson, ben-linux, w.sang, rob,
jeffrey.t.kirsher, akpm, davem, joe, ralf, dirk.brandewie,
jayachandranc, Xiangzhen.Ye, linux-doc, linux-kernel, linux-pci,
suzanne.decreme
On Fri, Jul 13, 2012 at 3:40 AM, Amaury Decrême
<amaury.decreme@gmail.com> wrote:
> This patch is a driver for SiS964 I2C bus.
>
> It was forked from i2c-sis630 and modified with SiS datasheets.
>
> Tested with kmemleak.
>
> Signed-off-by: Amaury Decrême <amaury.decreme@gmail.com>
> ---
> Documentation/i2c/busses/i2c-sis964 | 34 ++
> MAINTAINERS | 16 +
> drivers/i2c/busses/Kconfig | 12 +-
> drivers/i2c/busses/Makefile | 1 +
> drivers/i2c/busses/i2c-sis964.c | 575 +++++++++++++++++++++++++++++++++++
> include/linux/pci_ids.h | 1 +
> 6 files changed, 638 insertions(+), 1 deletions(-)
> create mode 100644 Documentation/i2c/busses/i2c-sis964
> create mode 100644 drivers/i2c/busses/i2c-sis964.c
>
> diff --git a/Documentation/i2c/busses/i2c-sis964 b/Documentation/i2c/busses/i2c-sis964
> new file mode 100644
> index 0000000..a831f1a
> --- /dev/null
> +++ b/Documentation/i2c/busses/i2c-sis964
> @@ -0,0 +1,34 @@
> +Kernel driver i2c-sis964
> +
> +Supported adapters:
> + * Silicon Integrated Systems Corp (SiS)
> + 964 chipset (Datasheet by SiS)
> + * Possible other SiS chipsets with the same registers and clocks
> +
> +Author: Amaury Decrême <amaury.decreme@gmail.com>
> +
> +Module Parameters
> +-----------------
> +
> +* force = [1|0] Forcibly enable the SIS964. DANGEROUS!
> + This can be interesting for chipsets not named
> + above to check if it works for you chipset,
> + but DANGEROUS!
> +
> +* low_clock = [1|0] 1 = Set Host Master Clock to 28KHz (defaut 56Khz)
> +
> +Description
> +-----------
> +
> +This SMBus driver is known to work on motherboards with the SiS964 chipset.
> +
> +If you see something like this:
> +
> +00:02.0 ISA bridge: Silicon Integrated Systems [SiS] SiS964 [MuTIOL Media IO]
> +
> +in your 'lspci' output , then this driver is for your chipset.
> +
> +Thank You
> +---------
> +Alexander Malysh <amalysh@web.de>
> +- Who has written i2c-sis630, from which i2c-sis964 is forked
> diff --git a/MAINTAINERS b/MAINTAINERS
> index eb22272..4a11805 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -6179,6 +6179,22 @@ S: Maintained
> F: Documentation/i2c/busses/i2c-sis96x
> F: drivers/i2c/busses/i2c-sis96x.c
>
> +SIS 964 I2C/SMBUS DRIVER
> +M: "Amaury Decrême" <amaury.decreme@gmail.com>
> +L: linux-i2c@vger.kernel.org
> +S: Maintained
> +F: Documentation/i2c/busses/i2c-sis96i4
> +F: drivers/i2c/busses/i2c-sis964.c
> +
> +SIS FRAMEBUFFER DRIVER
> +M: Thomas Winischhofer <thomas@winischhofer.net>
> +W: http://www.winischhofer.net/linuxsisvga.shtml
> +S: Maintained
> +F: Documentation/fb/sisfb.txt
> +F: drivers/video/sis/
> +F: include/video/sisfb.h
> +
> +SIS USB2VGA DRIVER
> SIS FRAMEBUFFER DRIVER
> M: Thomas Winischhofer <thomas@winischhofer.net>
> W: http://www.winischhofer.net/linuxsisvga.shtml
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index 7244c8b..8dc9f90 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -189,8 +189,18 @@ config I2C_SIS630
> This driver can also be built as a module. If so, the module
> will be called i2c-sis630.
>
> +config I2C_SIS964
> + tristate "SiS 964"
> + depends on PCI && EXPERIMENTAL
> + help
> + If you say yes to this option, support will be included for the SiS
> + 964 SMBus (a subset of I2C) interfaces.
> +
> + This driver can also be built as a module. If so, the module
> + will be called i2c-sis964.
> +
> config I2C_SIS96X
> - tristate "SiS 96x"
> + tristate "SiS 96x (but SiS964)"
> depends on PCI
> help
> If you say yes to this option, support will be included for the SiS
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index ce3c2be..b985bc8 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -19,6 +19,7 @@ obj-$(CONFIG_I2C_NFORCE2_S4985) += i2c-nforce2-s4985.o
> obj-$(CONFIG_I2C_PIIX4) += i2c-piix4.o
> obj-$(CONFIG_I2C_SIS5595) += i2c-sis5595.o
> obj-$(CONFIG_I2C_SIS630) += i2c-sis630.o
> +obj-$(CONFIG_I2C_SIS964) += i2c-sis964.o
> obj-$(CONFIG_I2C_SIS96X) += i2c-sis96x.o
> obj-$(CONFIG_I2C_VIA) += i2c-via.o
> obj-$(CONFIG_I2C_VIAPRO) += i2c-viapro.o
> diff --git a/drivers/i2c/busses/i2c-sis964.c b/drivers/i2c/busses/i2c-sis964.c
> new file mode 100644
> index 0000000..9f4ed14
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-sis964.c
> @@ -0,0 +1,575 @@
> +/*
> + Copyright (c) 2012 Amaury Decrême <amaury.decreme@gmail.com>
> +
> + This program is free software; you can redistribute it and/or modify
> + it under the terms of the GNU General Public License as published by
> + the Free Software Foundation; either version 2 of the License, or
> + (at your option) any later version.
> +
> + This program is distributed in the hope that it will be useful,
> + but WITHOUT ANY WARRANTY; without even the implied warranty of
> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + GNU General Public License for more details.
> +
> + You should have received a copy of the GNU General Public License
> + along with this program; if not, write to the Free Software
> + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
> +*/
> +
> +/*
> + Changes:
> + 11.08.2011
> + Fork of original i2c-sis630 - Alexander Malysh <amalysh@web.de>
> + Adapted for SiS964 with datasheets
> + - Amaury Decrême <amaury.decreme@gmail.com>
> +*/
> +
> +/*
> + Supports:
> + SIS 964
> +
> + Note: we assume there can only be one device, with one SMBus interface.
> +*/
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/delay.h>
> +#include <linux/pci.h>
> +#include <linux/ioport.h>
> +#include <linux/init.h>
> +#include <linux/i2c.h>
> +#include <linux/acpi.h>
> +#include <linux/io.h>
> +
> +/* SIS964 SMBus registers */
> +#define SMB_STS 0xE0 /* status */
> +#define SMB_EN 0xE1 /* status enable */
> +#define SMB_CNT 0xE2 /* Control */
> +#define SMBHOST_CNT 0xE3 /* Host Control */
> +#define SMB_ADDR 0xE4 /* Address */
> +#define SMB_CMD 0xE5 /* Command */
> +#define SMB_PERRCHK 0xE6 /* Packet Error Check */
> +#define SMB_COUNT 0xE7 /* Byte Count */
> +#define SMB_BYTE 0xE8 /* ~0x8F data byte field */
> +#define SMBDEV_ADDR 0xF0 /* Device Address */
> +#define SMB_DB0 0xF1 /* Device byte0 */
> +#define SMB_DB1 0xF2 /* Device byte1 */
> +#define SMB_SAA 0xF3 /* Host slave alias address */
> +#define SMB_PCOUNT 0xF4 /* processed byte count */
> +
> +
> +/* SMB_STS register */
> +#define SMBALT_STS 0x80 /* Slave alert */
> +#define BYTE_DONE_STS 0x10 /* Byte Done Status / Block Array */
> +#define SMBMAS_STS 0x08 /* Host Master */
> +#define SMBCOL_STS 0x04 /* Collision */
> +#define SMBERR_STS 0x02 /* Device error */
> +
> +/* SMB_CNT register */
> +#define SMBCLK_SEL 0x20 /* Host master clock selection */
> +#define SMB_PROBE 0x02 /* Bus Probe */
> +#define SMB_HOSTBUSY 0x01 /* Host Busy */
> +
> +/* SMBHOST_CNT register */
> +#define SMB_KILL 0x20 /* Kill */
> +#define SMB_START 0x10 /* Start */
> +#define SMB_PTL 0x07 /* Command Protocol */
> +
> +
> +/* SMB_ADDR register */
> +#define SMB_ADDRESS 0xFE /* Adress */
> +#define SMB_RW 0x01 /* Read/Write */
> +
> +
> +/* SMB_BYTE register */
> +#define SMB_BYTE0 0xFF /* Byte 0 */
> +#define SMB_BYTE1 0xFF00 /* Byte 1 */
> +
> +/* register count for request_region */
> +#define SIS964_SMB_IOREGION 21
> +
> +/* PCI address constants */
> +/* acpi base address register */
> +#define SIS964_ACPI_BASE_REG 0x74
> +/* bios control register */
> +#define SIS964_BIOS_CTL_REG 0x40
> +
> +/* Other settings */
> +#define MAX_TIMEOUT 500
> +
> +/* SIS964 constants */
> +#define SIS964_QUICK 0x00
> +#define SIS964_BYTE 0x01
> +#define SIS964_BYTE_DATA 0x02
> +#define SIS964_WORD_DATA 0x03
> +#define SIS964_PCALL 0x04
> +#define SIS964_BLOCK_DATA 0x05
> +
> +static struct pci_driver sis964_driver;
> +
> +/* insmod parameters */
> +static bool low_clock;
> +static bool force;
> +module_param(low_clock, bool, 0);
> +MODULE_PARM_DESC(low_clock, "Set Host Master Clock to 28KHz (default 56KHz).");
> +module_param(force, bool, 0);
> +MODULE_PARM_DESC(force, "Forcibly enable the SIS964. DANGEROUS!");
> +
> +/* acpi base address */
> +static unsigned short acpi_base;
> +
> +/* supported chips */
> +static int supported[] = {
> + PCI_DEVICE_ID_SI_964,
> + 0 /* terminates the list */
> +};
> +
> +static inline u8 sis964_read(u8 reg)
> +{
> + return inb(acpi_base + reg);
> +}
> +
> +static inline void sis964_write(u8 reg, u8 data)
> +{
> + outb(data, acpi_base + reg);
> +}
> +
> +static int sis964_transaction_start(struct i2c_adapter *adap,
> + int ptl, u8 *oldclock)
> +{
> + int tmp = 0;
> +
> + /* Clear status register */
> + sis964_write(SMB_STS, 0xFF);
> +
> + /* Make sure the SMBus host is ready to start transmitting. */
> + tmp = sis964_read(SMB_CNT);
> + if (tmp & (SMB_PROBE | SMB_HOSTBUSY)) {
> + dev_dbg(&adap->dev,
> + "Bus busy (status 0x%02x). Killing transaction.\n",
> + tmp);
> +
> + sis964_write(SMBHOST_CNT, SMB_KILL);
> +
> + return -EBUSY;
> + }
> +
> + /* Set Host Master Clock to 28KHz if requested */
> + if (low_clock) {
> + *oldclock = sis964_read(SMB_CNT);
> + sis964_write(SMB_CNT, SMBCLK_SEL);
> + }
> +
> + /* start the transaction by setting bit start and protocol */
> + sis964_write(SMBHOST_CNT, SMB_START | (ptl & SMB_PTL));
> +
> + return 0;
> +}
> +
> +static int sis964_transaction_wait(struct i2c_adapter *adap, int ptl)
> +{
> + int tmp = 0, timeout = 0;
> +
> + /* Wait 30us, valid for 28Khz and 56Khz */
> + udelay(30);
> +
> + tmp = sis964_read(SMB_STS);
> + if (!(tmp & SMB_PROBE) && (tmp & SMB_HOSTBUSY)) {
> + dev_dbg(&adap->dev,
> + "Host busy (status 0x%02x). Restarting transaction.\n",
> + tmp);
> + sis964_write(SMBHOST_CNT, SMB_KILL);
> + return -EAGAIN;
> + }
> +
> + while (!(ptl == SIS964_BLOCK_DATA && (tmp & BYTE_DONE_STS))
> + && !(tmp & (SMBMAS_STS | SMBCOL_STS | SMBERR_STS))
> + && (timeout++ < MAX_TIMEOUT)) {
> +
> + /* Datasheets: wait 4ms max at 28Khz and
> + * 2ms max at 56Khz for 8 bytes */
> + if (low_clock)
> + udelay(4000);
> + else
> + udelay(2000);
> + tmp = sis964_read(SMB_STS);
> + }
> +
> + /* If the SMBus is still busy, we give up */
> + if (timeout > MAX_TIMEOUT) {
> + dev_dbg(&adap->dev,
> + "Bus Timeout (status 0x%02x)!\n", tmp);
> + return -ETIMEDOUT;
> + }
> +
> + if (tmp & SMBERR_STS) {
> + dev_dbg(&adap->dev,
> + "Failed bus transaction (status 0x%02x)!\n", tmp);
> + return -ENXIO;
> + }
> +
> + if (tmp & SMBCOL_STS) {
> + dev_err(&adap->dev,
> + "Bus collision (status 0x%02x)!\n", tmp);
> + sis964_write(SMB_STS, tmp & ~SMBCOL_STS);
> + return -EAGAIN;
> + }
> +
> + return 0;
> +}
> +
> +static void sis964_transaction_end(u8 oldclock)
> +{
> + /* clear all status "sticky" bits */
> + sis964_write(SMB_STS, 0xFF);
> +
> + /* restore old Host Master Clock if low_clock is set */
> + if (low_clock)
> + sis964_write(SMB_CNT, oldclock & SMBCLK_SEL);
> +}
> +
> +static int sis964_transaction(struct i2c_adapter *adap, int ptl)
> +{
> + int tmp = 0, timeout = 0;
> + u8 oldclock = 0;
> +
> + do {
> + tmp = sis964_transaction_start(adap, ptl, &oldclock);
> + if (tmp)
> + return tmp;
> +
> + tmp = sis964_transaction_wait(adap, ptl);
> + sis964_transaction_end(oldclock);
> + } while (tmp == -EAGAIN && timeout++ < MAX_TIMEOUT);
> +
> + if (timeout > MAX_TIMEOUT) {
> + dev_dbg(&adap->dev, "Bus timeout !\n");
> + return -ETIMEDOUT;
> + }
> +
> + return 0;
> +}
> +
> +static int sis964_block_data_read(struct i2c_adapter *adap,
> + union i2c_smbus_data *data)
> +{
> + int i, len = 0, tmp = 0;
> + u8 oldclock = 0;
> +
> + data->block[0] = len = 0;
> +
> + tmp = sis964_transaction_start(adap, SIS964_BLOCK_DATA, &oldclock);
> + if (tmp)
> + return tmp;
> +
> + do {
> + tmp = sis964_transaction_wait(adap, SIS964_BLOCK_DATA);
> + if (tmp) {
> + dev_dbg(&adap->dev, "Transaction wait failed\n");
> + break;
> + }
> +
> + /* if this first transaction then read byte count */
> + if (len == 0)
> + data->block[0] = sis964_read(SMB_COUNT);
> +
> + if (data->block[0] > 32)
> + data->block[0] = 32;
> +
> + dev_dbg(&adap->dev, "Block data read len=0x%x\n",
> + data->block[0]);
> +
> + for (i = 0; i < 8 && len < data->block[0]; i++, len++) {
> + dev_dbg(&adap->dev, "Read i=%d len=%d\n", i, len);
> + data->block[len+1] = sis964_read(SMB_BYTE+i);
> + }
> +
> + /* clear BYTE_DONE_STS */
> + sis964_write(SMB_STS, BYTE_DONE_STS);
> + } while (len < data->block[0]);
> +
> + sis964_transaction_end(oldclock);
> +
> + return 0;
> +}
> +
> +
> +static int sis964_block_data_write(struct i2c_adapter *adap,
> + union i2c_smbus_data *data)
> +{
> +
> + int i, len = 0, tmp = 0;
> + u8 oldclock = 0;
> +
> + len = data->block[0];
> + if (len < 0)
> + len = 0;
> + else if (len > 32)
> + len = 32;
> +
> + sis964_write(SMB_COUNT, len);
> +
> + for (i = 1; i <= len; i++) {
> + dev_dbg(&adap->dev, "Set data 0x%02x\n", data->block[i]);
> +
> + /* set data */
> + sis964_write(SMB_BYTE+(i-1)%8, data->block[i]);
> + if (i == 8 || (len < 8 && i == len)) {
> +
> + /* first transaction */
> + tmp = sis964_transaction_start(adap, SIS964_BLOCK_DATA,
> + &oldclock);
> + if (tmp)
> + return tmp;
> +
> + } else if ((i-1)%8 == 7 || i == len) {
> + if (i > 8) {
> + dev_dbg(&adap->dev,
> + "Clear smbary_sts len=%d i=%d\n", len, i);
> +
> + /*
> + If this is not first transaction,
> + we must clear sticky bit.
> + clear BYTE_DONE-STS
> + */
> + sis964_write(SMB_STS, BYTE_DONE_STS);
> + }
> + tmp = sis964_transaction_wait(adap,
> + SIS964_BLOCK_DATA);
> + if (tmp) {
> + dev_dbg(&adap->dev,
> + "Transaction wait failed\n");
> + break;
> + }
> + }
> + }
> +
> + sis964_transaction_end(oldclock);
> +
> + return 0;
> +}
> +
> +static int sis964_block_data(struct i2c_adapter *adap,
> + union i2c_smbus_data *data, int read_write)
> +{
> + if (read_write == I2C_SMBUS_WRITE)
> + return sis964_block_data_write(adap, data);
> + else
> + return sis964_block_data_read(adap, data);
> +}
> +
> +/* Return negative errno on error. */
> +static s32 sis964_access(struct i2c_adapter *adap, u16 addr,
> + unsigned short flags, char read_write,
> + u8 command, int ptl, union i2c_smbus_data *data)
> +{
> + int tmp = 0;
> +
> + switch (ptl) {
> + case I2C_SMBUS_QUICK:
> + sis964_write(SMB_ADDR, ((addr << 1) & SMB_ADDRESS) |
> + (read_write & SMB_RW));
> + ptl = SIS964_QUICK;
> + break;
> + case I2C_SMBUS_BYTE:
> + sis964_write(SMB_ADDR, ((addr << 1) & SMB_ADDRESS) |
> + (read_write & SMB_RW));
> + if (read_write == I2C_SMBUS_WRITE)
> + sis964_write(SMB_CMD, command);
> + ptl = SIS964_BYTE;
> + break;
> + case I2C_SMBUS_BYTE_DATA:
> + sis964_write(SMB_ADDR, ((addr << 1) & SMB_ADDRESS) |
> + (read_write & SMB_RW));
> + sis964_write(SMB_CMD, command);
> + if (read_write == I2C_SMBUS_WRITE)
> + sis964_write(SMB_BYTE, data->byte);
> + ptl = SIS964_BYTE_DATA;
> + break;
> + case I2C_SMBUS_PROC_CALL:
> + case I2C_SMBUS_WORD_DATA:
> + sis964_write(SMB_ADDR, ((addr << 1) & SMB_ADDRESS) |
> + (read_write & SMB_RW));
> + sis964_write(SMB_CMD, command);
> + if (read_write == I2C_SMBUS_WRITE) {
> + sis964_write(SMB_BYTE, data->word & SMB_BYTE0);
> + sis964_write(SMB_BYTE + 1,
> + (data->word & SMB_BYTE1) >> 8);
> + }
> + ptl = (ptl == I2C_SMBUS_PROC_CALL ?
> + SIS964_PCALL : SIS964_WORD_DATA);
> + break;
> + case I2C_SMBUS_BLOCK_DATA:
> + sis964_write(SMB_ADDR, ((addr << 1) & SMB_ADDRESS) |
> + (read_write & SMB_RW));
> + sis964_write(SMB_CMD, command);
> + ptl = SIS964_BLOCK_DATA;
> + return sis964_block_data(adap, data, read_write);
> + default:
> + dev_warn(&adap->dev, "Unsupported transaction %d\n",
> + ptl);
> + return -EOPNOTSUPP;
> + }
> +
> + tmp = sis964_transaction(adap, ptl);
> + if (tmp)
> + return tmp;
> +
> + if (ptl != SIS964_PCALL &&
> + (read_write == I2C_SMBUS_WRITE || ptl == SIS964_QUICK)) {
> + return 0;
> + }
> +
> + switch (ptl) {
> + case SIS964_BYTE:
> + case SIS964_BYTE_DATA:
> + data->byte = sis964_read(SMB_BYTE);
> + break;
> + case SIS964_PCALL:
> + case SIS964_WORD_DATA:
> + data->word = sis964_read(SMB_BYTE) +
> + (sis964_read(SMB_BYTE + 1) << 8);
> + break;
> + }
> +
> + return 0;
> +}
> +
> +static u32 sis964_func(struct i2c_adapter *adapter)
> +{
> + /* SMBus Command protocol supported */
> + return I2C_FUNC_SMBUS_QUICK | /* Quick command */
> + I2C_FUNC_SMBUS_BYTE | /* Send/Receive Byte */
> + I2C_FUNC_SMBUS_BYTE_DATA | /* Read/Write Byte Data */
> + I2C_FUNC_SMBUS_WORD_DATA | /* Read/Write Word Data */
> + I2C_FUNC_SMBUS_PROC_CALL | /* Process Call */
> + I2C_FUNC_SMBUS_BLOCK_DATA; /* Read/Write Block Data */
> +}
> +
> +static int __devinit sis964_setup(struct pci_dev *sis964_dev)
> +{
> + unsigned char b;
> + struct pci_dev *dummy = NULL;
> + int tmp = 0, i;
> +
> + /* check for supported SiS devices */
> + for (i = 0; supported[i] > 0 && dummy == NULL; i++)
> + dummy = pci_get_device(PCI_VENDOR_ID_SI, supported[i], dummy);
> +
> + if (dummy) {
> + pci_dev_put(dummy);
> + } else if (force) {
> + dev_err(&sis964_dev->dev,
> + "WARNING: Can't detect SIS964 compatible device, but "
> + "loading because of force option enabled\n");
> + } else {
> + return -ENODEV;
> + }
> +
> +
> + /*
> + Enable ACPI first , so we can accsess reg 74-75
> + in acpi io space and read acpi base addr
> + */
> + if (pci_read_config_byte(sis964_dev, SIS964_BIOS_CTL_REG, &b)) {
> + dev_err(&sis964_dev->dev, "Error: Can't read bios ctl reg\n");
> + return -ENODEV;
> + }
> + /* if ACPI already enabled , do nothing */
> + if (!(b & 0x80) &&
> + pci_write_config_byte(sis964_dev, SIS964_BIOS_CTL_REG, b | 0x80)) {
> + dev_err(&sis964_dev->dev, "Error: Can't enable ACPI\n");
> + return -ENODEV;
> + }
> +
> + /* Determine the ACPI base address */
> + if (pci_read_config_word(sis964_dev, SIS964_ACPI_BASE_REG,
> + &acpi_base)) {
> + dev_err(&sis964_dev->dev,
> + "Error: Can't determine ACPI base address\n");
> + return -ENODEV;
> + }
> +
> + dev_dbg(&sis964_dev->dev, "ACPI base at 0x%04x\n", acpi_base);
> +
> + tmp = acpi_check_region(acpi_base + SMB_STS, SIS964_SMB_IOREGION,
> + sis964_driver.name);
> + if (tmp) {
> + acpi_base = 0;
> + return -ENODEV;
> + }
> +
> + /* Everything is happy, let's grab the memory and set things up. */
> + if (!request_region(acpi_base + SMB_STS, SIS964_SMB_IOREGION,
> + sis964_driver.name)) {
> + dev_err(&sis964_dev->dev,
> + "SMBus registers 0x%04x-0x%04x already in use!\n",
> + acpi_base + SMB_STS, acpi_base + SMB_SAA);
> + acpi_base = 0;
> + return -ENODEV;
> + }
> +
> + return 0;
> +}
> +
> +
> +static const struct i2c_algorithm smbus_algorithm = {
> + .smbus_xfer = sis964_access,
> + .functionality = sis964_func,
> +};
> +
> +static struct i2c_adapter sis964_adapter = {
> + .owner = THIS_MODULE,
> + .class = I2C_CLASS_HWMON | I2C_CLASS_SPD,
> + .algo = &smbus_algorithm,
> +};
> +
> +static DEFINE_PCI_DEVICE_TABLE(sis964_ids) = {
> + { PCI_DEVICE(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_964) },
> + { 0 }
> +};
> +
> +MODULE_DEVICE_TABLE(pci, sis964_ids);
> +
> +static int __devinit sis964_probe(struct pci_dev *dev,
> + const struct pci_device_id *id)
> +{
> + if (sis964_setup(dev)) {
> + dev_err(&dev->dev,
> + "SIS964 comp. bus not detected, module not inserted.\n");
> + return -ENODEV;
> + }
> +
> + /* set up the sysfs linkage to our parent device */
> + sis964_adapter.dev.parent = &dev->dev;
> +
> + snprintf(sis964_adapter.name, sizeof(sis964_adapter.name),
> + "SMBus SIS964 adapter at %04xh", acpi_base + SMB_STS);
> +
> + return i2c_add_adapter(&sis964_adapter);
> +}
> +
> +static void __devexit sis964_remove(struct pci_dev *dev)
> +{
> + dev_dbg(&dev->dev, "sis964_remove");
> +
> + if (acpi_base) {
> + i2c_del_adapter(&sis964_adapter);
> + release_region(acpi_base + SMB_STS, SIS964_SMB_IOREGION);
> + acpi_base = 0;
> + }
> +}
> +
> +
> +static struct pci_driver sis964_driver = {
> + .name = "sis964_smbus",
> + .id_table = sis964_ids,
> + .probe = sis964_probe,
> + .remove = __devexit_p(sis964_remove),
> +};
> +
> +module_pci_driver(sis964_driver);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Amaury Decrême <amaury.decreme@gmail.com>");
> +MODULE_DESCRIPTION("SiS964 SMBus driver");
> diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h
> index ab741b0..0ffc982 100644
> --- a/include/linux/pci_ids.h
> +++ b/include/linux/pci_ids.h
> @@ -699,6 +699,7 @@
> #define PCI_DEVICE_ID_SI_961 0x0961
> #define PCI_DEVICE_ID_SI_962 0x0962
> #define PCI_DEVICE_ID_SI_963 0x0963
> +#define PCI_DEVICE_ID_SI_964 0x0964
Please read the comment at the top of this file; I don't think this
addition qualifies as something that should be added.
> #define PCI_DEVICE_ID_SI_965 0x0965
> #define PCI_DEVICE_ID_SI_966 0x0966
> #define PCI_DEVICE_ID_SI_968 0x0968
> --
> 1.7.8.6
>
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH] i2c: sis964: bus driver
2012-07-13 15:36 ` Bjorn Helgaas
@ 2012-07-14 7:53 ` Amaury Decrême
2012-07-15 11:35 ` Jean Delvare
1 sibling, 0 replies; 5+ messages in thread
From: Amaury Decrême @ 2012-07-14 7:53 UTC (permalink / raw)
To: Bjorn Helgaas
Cc: khali, linux-i2c, amalysh, nelson, ben-linux, w.sang, rob,
jeffrey.t.kirsher, akpm, davem, joe, ralf, dirk.brandewie,
jayachandranc, Xiangzhen.Ye, linux-doc, linux-kernel, linux-pci,
suzanne.decreme
(sorry for the spam, I needed to resend in text format...)
Hello,
You're right. In fact, I was thinking of add code in drivers/pci/quirk.c to unhide the SMBus for users who maybe needed it with the following code:
--- a/drivers/pci/quirks.c
+++ b/drivers/pci/quirks.c
@@ -1389,6 +1389,24 @@ DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_962, quirk_si
DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_963, quirk_sis_96x_smbus);
DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_LPC, quirk_sis_96x_smbus);
+
+/*
+ * SiS 964 south bridge: BIOS typically hides SMBus device...
+ */
+static void quirk_sis_964_smbus(struct pci_dev *dev)
+{
+ u8 val = 0;
+ pci_read_config_byte(dev, 0x76, &val);
+ if (!(val & 0x01)) {
+ dev_info(&dev->dev, "Enabling SiS 964 SMBus\n");
+ pci_write_config_byte(dev, 0x76, val | 0x01);
+ }
+}
+DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_964,
+ quirk_sis_964_smbus);
+DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_964,
+ quirk_sis_964_smbus);
Still, I'm not quite sure if it is needed.
Thanks for the comments.
Le 13 juil. 2012 à 17:36, Bjorn Helgaas a écrit :
> On Fri, Jul 13, 2012 at 3:40 AM, Amaury Decrême
> <amaury.decreme@gmail.com> wrote:
>> This patch is a driver for SiS964 I2C bus.
>>
>> It was forked from i2c-sis630 and modified with SiS datasheets.
>>
>> Tested with kmemleak.
>>
>> Signed-off-by: Amaury Decrême <amaury.decreme@gmail.com>
>> ---
>> Documentation/i2c/busses/i2c-sis964 | 34 ++
>> MAINTAINERS | 16 +
>> drivers/i2c/busses/Kconfig | 12 +-
>> drivers/i2c/busses/Makefile | 1 +
>> drivers/i2c/busses/i2c-sis964.c | 575 +++++++++++++++++++++++++++++++++++
>> include/linux/pci_ids.h | 1 +
>> 6 files changed, 638 insertions(+), 1 deletions(-)
>> create mode 100644 Documentation/i2c/busses/i2c-sis964
>> create mode 100644 drivers/i2c/busses/i2c-sis964.c
>>
>> diff --git a/Documentation/i2c/busses/i2c-sis964 b/Documentation/i2c/busses/i2c-sis964
>> new file mode 100644
>> index 0000000..a831f1a
>> --- /dev/null
>> +++ b/Documentation/i2c/busses/i2c-sis964
>> @@ -0,0 +1,34 @@
>> +Kernel driver i2c-sis964
>> +
>> +Supported adapters:
>> + * Silicon Integrated Systems Corp (SiS)
>> + 964 chipset (Datasheet by SiS)
>> + * Possible other SiS chipsets with the same registers and clocks
>> +
>> +Author: Amaury Decrême <amaury.decreme@gmail.com>
>> +
>> +Module Parameters
>> +-----------------
>> +
>> +* force = [1|0] Forcibly enable the SIS964. DANGEROUS!
>> + This can be interesting for chipsets not named
>> + above to check if it works for you chipset,
>> + but DANGEROUS!
>> +
>> +* low_clock = [1|0] 1 = Set Host Master Clock to 28KHz (defaut 56Khz)
>> +
>> +Description
>> +-----------
>> +
>> +This SMBus driver is known to work on motherboards with the SiS964 chipset.
>> +
>> +If you see something like this:
>> +
>> +00:02.0 ISA bridge: Silicon Integrated Systems [SiS] SiS964 [MuTIOL Media IO]
>> +
>> +in your 'lspci' output , then this driver is for your chipset.
>> +
>> +Thank You
>> +---------
>> +Alexander Malysh <amalysh@web.de>
>> +- Who has written i2c-sis630, from which i2c-sis964 is forked
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index eb22272..4a11805 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -6179,6 +6179,22 @@ S: Maintained
>> F: Documentation/i2c/busses/i2c-sis96x
>> F: drivers/i2c/busses/i2c-sis96x.c
>>
>> +SIS 964 I2C/SMBUS DRIVER
>> +M: "Amaury Decrême" <amaury.decreme@gmail.com>
>> +L: linux-i2c@vger.kernel.org
>> +S: Maintained
>> +F: Documentation/i2c/busses/i2c-sis96i4
>> +F: drivers/i2c/busses/i2c-sis964.c
>> +
>> +SIS FRAMEBUFFER DRIVER
>> +M: Thomas Winischhofer <thomas@winischhofer.net>
>> +W: http://www.winischhofer.net/linuxsisvga.shtml
>> +S: Maintained
>> +F: Documentation/fb/sisfb.txt
>> +F: drivers/video/sis/
>> +F: include/video/sisfb.h
>> +
>> +SIS USB2VGA DRIVER
>> SIS FRAMEBUFFER DRIVER
>> M: Thomas Winischhofer <thomas@winischhofer.net>
>> W: http://www.winischhofer.net/linuxsisvga.shtml
>> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
>> index 7244c8b..8dc9f90 100644
>> --- a/drivers/i2c/busses/Kconfig
>> +++ b/drivers/i2c/busses/Kconfig
>> @@ -189,8 +189,18 @@ config I2C_SIS630
>> This driver can also be built as a module. If so, the module
>> will be called i2c-sis630.
>>
>> +config I2C_SIS964
>> + tristate "SiS 964"
>> + depends on PCI && EXPERIMENTAL
>> + help
>> + If you say yes to this option, support will be included for the SiS
>> + 964 SMBus (a subset of I2C) interfaces.
>> +
>> + This driver can also be built as a module. If so, the module
>> + will be called i2c-sis964.
>> +
>> config I2C_SIS96X
>> - tristate "SiS 96x"
>> + tristate "SiS 96x (but SiS964)"
>> depends on PCI
>> help
>> If you say yes to this option, support will be included for the SiS
>> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
>> index ce3c2be..b985bc8 100644
>> --- a/drivers/i2c/busses/Makefile
>> +++ b/drivers/i2c/busses/Makefile
>> @@ -19,6 +19,7 @@ obj-$(CONFIG_I2C_NFORCE2_S4985) += i2c-nforce2-s4985.o
>> obj-$(CONFIG_I2C_PIIX4) += i2c-piix4.o
>> obj-$(CONFIG_I2C_SIS5595) += i2c-sis5595.o
>> obj-$(CONFIG_I2C_SIS630) += i2c-sis630.o
>> +obj-$(CONFIG_I2C_SIS964) += i2c-sis964.o
>> obj-$(CONFIG_I2C_SIS96X) += i2c-sis96x.o
>> obj-$(CONFIG_I2C_VIA) += i2c-via.o
>> obj-$(CONFIG_I2C_VIAPRO) += i2c-viapro.o
>> diff --git a/drivers/i2c/busses/i2c-sis964.c b/drivers/i2c/busses/i2c-sis964.c
>> new file mode 100644
>> index 0000000..9f4ed14
>> --- /dev/null
>> +++ b/drivers/i2c/busses/i2c-sis964.c
>> @@ -0,0 +1,575 @@
>> +/*
>> + Copyright (c) 2012 Amaury Decrême <amaury.decreme@gmail.com>
>> +
>> + This program is free software; you can redistribute it and/or modify
>> + it under the terms of the GNU General Public License as published by
>> + the Free Software Foundation; either version 2 of the License, or
>> + (at your option) any later version.
>> +
>> + This program is distributed in the hope that it will be useful,
>> + but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
>> + GNU General Public License for more details.
>> +
>> + You should have received a copy of the GNU General Public License
>> + along with this program; if not, write to the Free Software
>> + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
>> +*/
>> +
>> +/*
>> + Changes:
>> + 11.08.2011
>> + Fork of original i2c-sis630 - Alexander Malysh <amalysh@web.de>
>> + Adapted for SiS964 with datasheets
>> + - Amaury Decrême <amaury.decreme@gmail.com>
>> +*/
>> +
>> +/*
>> + Supports:
>> + SIS 964
>> +
>> + Note: we assume there can only be one device, with one SMBus interface.
>> +*/
>> +
>> +#include <linux/kernel.h>
>> +#include <linux/module.h>
>> +#include <linux/delay.h>
>> +#include <linux/pci.h>
>> +#include <linux/ioport.h>
>> +#include <linux/init.h>
>> +#include <linux/i2c.h>
>> +#include <linux/acpi.h>
>> +#include <linux/io.h>
>> +
>> +/* SIS964 SMBus registers */
>> +#define SMB_STS 0xE0 /* status */
>> +#define SMB_EN 0xE1 /* status enable */
>> +#define SMB_CNT 0xE2 /* Control */
>> +#define SMBHOST_CNT 0xE3 /* Host Control */
>> +#define SMB_ADDR 0xE4 /* Address */
>> +#define SMB_CMD 0xE5 /* Command */
>> +#define SMB_PERRCHK 0xE6 /* Packet Error Check */
>> +#define SMB_COUNT 0xE7 /* Byte Count */
>> +#define SMB_BYTE 0xE8 /* ~0x8F data byte field */
>> +#define SMBDEV_ADDR 0xF0 /* Device Address */
>> +#define SMB_DB0 0xF1 /* Device byte0 */
>> +#define SMB_DB1 0xF2 /* Device byte1 */
>> +#define SMB_SAA 0xF3 /* Host slave alias address */
>> +#define SMB_PCOUNT 0xF4 /* processed byte count */
>> +
>> +
>> +/* SMB_STS register */
>> +#define SMBALT_STS 0x80 /* Slave alert */
>> +#define BYTE_DONE_STS 0x10 /* Byte Done Status / Block Array */
>> +#define SMBMAS_STS 0x08 /* Host Master */
>> +#define SMBCOL_STS 0x04 /* Collision */
>> +#define SMBERR_STS 0x02 /* Device error */
>> +
>> +/* SMB_CNT register */
>> +#define SMBCLK_SEL 0x20 /* Host master clock selection */
>> +#define SMB_PROBE 0x02 /* Bus Probe */
>> +#define SMB_HOSTBUSY 0x01 /* Host Busy */
>> +
>> +/* SMBHOST_CNT register */
>> +#define SMB_KILL 0x20 /* Kill */
>> +#define SMB_START 0x10 /* Start */
>> +#define SMB_PTL 0x07 /* Command Protocol */
>> +
>> +
>> +/* SMB_ADDR register */
>> +#define SMB_ADDRESS 0xFE /* Adress */
>> +#define SMB_RW 0x01 /* Read/Write */
>> +
>> +
>> +/* SMB_BYTE register */
>> +#define SMB_BYTE0 0xFF /* Byte 0 */
>> +#define SMB_BYTE1 0xFF00 /* Byte 1 */
>> +
>> +/* register count for request_region */
>> +#define SIS964_SMB_IOREGION 21
>> +
>> +/* PCI address constants */
>> +/* acpi base address register */
>> +#define SIS964_ACPI_BASE_REG 0x74
>> +/* bios control register */
>> +#define SIS964_BIOS_CTL_REG 0x40
>> +
>> +/* Other settings */
>> +#define MAX_TIMEOUT 500
>> +
>> +/* SIS964 constants */
>> +#define SIS964_QUICK 0x00
>> +#define SIS964_BYTE 0x01
>> +#define SIS964_BYTE_DATA 0x02
>> +#define SIS964_WORD_DATA 0x03
>> +#define SIS964_PCALL 0x04
>> +#define SIS964_BLOCK_DATA 0x05
>> +
>> +static struct pci_driver sis964_driver;
>> +
>> +/* insmod parameters */
>> +static bool low_clock;
>> +static bool force;
>> +module_param(low_clock, bool, 0);
>> +MODULE_PARM_DESC(low_clock, "Set Host Master Clock to 28KHz (default 56KHz).");
>> +module_param(force, bool, 0);
>> +MODULE_PARM_DESC(force, "Forcibly enable the SIS964. DANGEROUS!");
>> +
>> +/* acpi base address */
>> +static unsigned short acpi_base;
>> +
>> +/* supported chips */
>> +static int supported[] = {
>> + PCI_DEVICE_ID_SI_964,
>> + 0 /* terminates the list */
>> +};
>> +
>> +static inline u8 sis964_read(u8 reg)
>> +{
>> + return inb(acpi_base + reg);
>> +}
>> +
>> +static inline void sis964_write(u8 reg, u8 data)
>> +{
>> + outb(data, acpi_base + reg);
>> +}
>> +
>> +static int sis964_transaction_start(struct i2c_adapter *adap,
>> + int ptl, u8 *oldclock)
>> +{
>> + int tmp = 0;
>> +
>> + /* Clear status register */
>> + sis964_write(SMB_STS, 0xFF);
>> +
>> + /* Make sure the SMBus host is ready to start transmitting. */
>> + tmp = sis964_read(SMB_CNT);
>> + if (tmp & (SMB_PROBE | SMB_HOSTBUSY)) {
>> + dev_dbg(&adap->dev,
>> + "Bus busy (status 0x%02x). Killing transaction.\n",
>> + tmp);
>> +
>> + sis964_write(SMBHOST_CNT, SMB_KILL);
>> +
>> + return -EBUSY;
>> + }
>> +
>> + /* Set Host Master Clock to 28KHz if requested */
>> + if (low_clock) {
>> + *oldclock = sis964_read(SMB_CNT);
>> + sis964_write(SMB_CNT, SMBCLK_SEL);
>> + }
>> +
>> + /* start the transaction by setting bit start and protocol */
>> + sis964_write(SMBHOST_CNT, SMB_START | (ptl & SMB_PTL));
>> +
>> + return 0;
>> +}
>> +
>> +static int sis964_transaction_wait(struct i2c_adapter *adap, int ptl)
>> +{
>> + int tmp = 0, timeout = 0;
>> +
>> + /* Wait 30us, valid for 28Khz and 56Khz */
>> + udelay(30);
>> +
>> + tmp = sis964_read(SMB_STS);
>> + if (!(tmp & SMB_PROBE) && (tmp & SMB_HOSTBUSY)) {
>> + dev_dbg(&adap->dev,
>> + "Host busy (status 0x%02x). Restarting transaction.\n",
>> + tmp);
>> + sis964_write(SMBHOST_CNT, SMB_KILL);
>> + return -EAGAIN;
>> + }
>> +
>> + while (!(ptl == SIS964_BLOCK_DATA && (tmp & BYTE_DONE_STS))
>> + && !(tmp & (SMBMAS_STS | SMBCOL_STS | SMBERR_STS))
>> + && (timeout++ < MAX_TIMEOUT)) {
>> +
>> + /* Datasheets: wait 4ms max at 28Khz and
>> + * 2ms max at 56Khz for 8 bytes */
>> + if (low_clock)
>> + udelay(4000);
>> + else
>> + udelay(2000);
>> + tmp = sis964_read(SMB_STS);
>> + }
>> +
>> + /* If the SMBus is still busy, we give up */
>> + if (timeout > MAX_TIMEOUT) {
>> + dev_dbg(&adap->dev,
>> + "Bus Timeout (status 0x%02x)!\n", tmp);
>> + return -ETIMEDOUT;
>> + }
>> +
>> + if (tmp & SMBERR_STS) {
>> + dev_dbg(&adap->dev,
>> + "Failed bus transaction (status 0x%02x)!\n", tmp);
>> + return -ENXIO;
>> + }
>> +
>> + if (tmp & SMBCOL_STS) {
>> + dev_err(&adap->dev,
>> + "Bus collision (status 0x%02x)!\n", tmp);
>> + sis964_write(SMB_STS, tmp & ~SMBCOL_STS);
>> + return -EAGAIN;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static void sis964_transaction_end(u8 oldclock)
>> +{
>> + /* clear all status "sticky" bits */
>> + sis964_write(SMB_STS, 0xFF);
>> +
>> + /* restore old Host Master Clock if low_clock is set */
>> + if (low_clock)
>> + sis964_write(SMB_CNT, oldclock & SMBCLK_SEL);
>> +}
>> +
>> +static int sis964_transaction(struct i2c_adapter *adap, int ptl)
>> +{
>> + int tmp = 0, timeout = 0;
>> + u8 oldclock = 0;
>> +
>> + do {
>> + tmp = sis964_transaction_start(adap, ptl, &oldclock);
>> + if (tmp)
>> + return tmp;
>> +
>> + tmp = sis964_transaction_wait(adap, ptl);
>> + sis964_transaction_end(oldclock);
>> + } while (tmp == -EAGAIN && timeout++ < MAX_TIMEOUT);
>> +
>> + if (timeout > MAX_TIMEOUT) {
>> + dev_dbg(&adap->dev, "Bus timeout !\n");
>> + return -ETIMEDOUT;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static int sis964_block_data_read(struct i2c_adapter *adap,
>> + union i2c_smbus_data *data)
>> +{
>> + int i, len = 0, tmp = 0;
>> + u8 oldclock = 0;
>> +
>> + data->block[0] = len = 0;
>> +
>> + tmp = sis964_transaction_start(adap, SIS964_BLOCK_DATA, &oldclock);
>> + if (tmp)
>> + return tmp;
>> +
>> + do {
>> + tmp = sis964_transaction_wait(adap, SIS964_BLOCK_DATA);
>> + if (tmp) {
>> + dev_dbg(&adap->dev, "Transaction wait failed\n");
>> + break;
>> + }
>> +
>> + /* if this first transaction then read byte count */
>> + if (len == 0)
>> + data->block[0] = sis964_read(SMB_COUNT);
>> +
>> + if (data->block[0] > 32)
>> + data->block[0] = 32;
>> +
>> + dev_dbg(&adap->dev, "Block data read len=0x%x\n",
>> + data->block[0]);
>> +
>> + for (i = 0; i < 8 && len < data->block[0]; i++, len++) {
>> + dev_dbg(&adap->dev, "Read i=%d len=%d\n", i, len);
>> + data->block[len+1] = sis964_read(SMB_BYTE+i);
>> + }
>> +
>> + /* clear BYTE_DONE_STS */
>> + sis964_write(SMB_STS, BYTE_DONE_STS);
>> + } while (len < data->block[0]);
>> +
>> + sis964_transaction_end(oldclock);
>> +
>> + return 0;
>> +}
>> +
>> +
>> +static int sis964_block_data_write(struct i2c_adapter *adap,
>> + union i2c_smbus_data *data)
>> +{
>> +
>> + int i, len = 0, tmp = 0;
>> + u8 oldclock = 0;
>> +
>> + len = data->block[0];
>> + if (len < 0)
>> + len = 0;
>> + else if (len > 32)
>> + len = 32;
>> +
>> + sis964_write(SMB_COUNT, len);
>> +
>> + for (i = 1; i <= len; i++) {
>> + dev_dbg(&adap->dev, "Set data 0x%02x\n", data->block[i]);
>> +
>> + /* set data */
>> + sis964_write(SMB_BYTE+(i-1)%8, data->block[i]);
>> + if (i == 8 || (len < 8 && i == len)) {
>> +
>> + /* first transaction */
>> + tmp = sis964_transaction_start(adap, SIS964_BLOCK_DATA,
>> + &oldclock);
>> + if (tmp)
>> + return tmp;
>> +
>> + } else if ((i-1)%8 == 7 || i == len) {
>> + if (i > 8) {
>> + dev_dbg(&adap->dev,
>> + "Clear smbary_sts len=%d i=%d\n", len, i);
>> +
>> + /*
>> + If this is not first transaction,
>> + we must clear sticky bit.
>> + clear BYTE_DONE-STS
>> + */
>> + sis964_write(SMB_STS, BYTE_DONE_STS);
>> + }
>> + tmp = sis964_transaction_wait(adap,
>> + SIS964_BLOCK_DATA);
>> + if (tmp) {
>> + dev_dbg(&adap->dev,
>> + "Transaction wait failed\n");
>> + break;
>> + }
>> + }
>> + }
>> +
>> + sis964_transaction_end(oldclock);
>> +
>> + return 0;
>> +}
>> +
>> +static int sis964_block_data(struct i2c_adapter *adap,
>> + union i2c_smbus_data *data, int read_write)
>> +{
>> + if (read_write == I2C_SMBUS_WRITE)
>> + return sis964_block_data_write(adap, data);
>> + else
>> + return sis964_block_data_read(adap, data);
>> +}
>> +
>> +/* Return negative errno on error. */
>> +static s32 sis964_access(struct i2c_adapter *adap, u16 addr,
>> + unsigned short flags, char read_write,
>> + u8 command, int ptl, union i2c_smbus_data *data)
>> +{
>> + int tmp = 0;
>> +
>> + switch (ptl) {
>> + case I2C_SMBUS_QUICK:
>> + sis964_write(SMB_ADDR, ((addr << 1) & SMB_ADDRESS) |
>> + (read_write & SMB_RW));
>> + ptl = SIS964_QUICK;
>> + break;
>> + case I2C_SMBUS_BYTE:
>> + sis964_write(SMB_ADDR, ((addr << 1) & SMB_ADDRESS) |
>> + (read_write & SMB_RW));
>> + if (read_write == I2C_SMBUS_WRITE)
>> + sis964_write(SMB_CMD, command);
>> + ptl = SIS964_BYTE;
>> + break;
>> + case I2C_SMBUS_BYTE_DATA:
>> + sis964_write(SMB_ADDR, ((addr << 1) & SMB_ADDRESS) |
>> + (read_write & SMB_RW));
>> + sis964_write(SMB_CMD, command);
>> + if (read_write == I2C_SMBUS_WRITE)
>> + sis964_write(SMB_BYTE, data->byte);
>> + ptl = SIS964_BYTE_DATA;
>> + break;
>> + case I2C_SMBUS_PROC_CALL:
>> + case I2C_SMBUS_WORD_DATA:
>> + sis964_write(SMB_ADDR, ((addr << 1) & SMB_ADDRESS) |
>> + (read_write & SMB_RW));
>> + sis964_write(SMB_CMD, command);
>> + if (read_write == I2C_SMBUS_WRITE) {
>> + sis964_write(SMB_BYTE, data->word & SMB_BYTE0);
>> + sis964_write(SMB_BYTE + 1,
>> + (data->word & SMB_BYTE1) >> 8);
>> + }
>> + ptl = (ptl == I2C_SMBUS_PROC_CALL ?
>> + SIS964_PCALL : SIS964_WORD_DATA);
>> + break;
>> + case I2C_SMBUS_BLOCK_DATA:
>> + sis964_write(SMB_ADDR, ((addr << 1) & SMB_ADDRESS) |
>> + (read_write & SMB_RW));
>> + sis964_write(SMB_CMD, command);
>> + ptl = SIS964_BLOCK_DATA;
>> + return sis964_block_data(adap, data, read_write);
>> + default:
>> + dev_warn(&adap->dev, "Unsupported transaction %d\n",
>> + ptl);
>> + return -EOPNOTSUPP;
>> + }
>> +
>> + tmp = sis964_transaction(adap, ptl);
>> + if (tmp)
>> + return tmp;
>> +
>> + if (ptl != SIS964_PCALL &&
>> + (read_write == I2C_SMBUS_WRITE || ptl == SIS964_QUICK)) {
>> + return 0;
>> + }
>> +
>> + switch (ptl) {
>> + case SIS964_BYTE:
>> + case SIS964_BYTE_DATA:
>> + data->byte = sis964_read(SMB_BYTE);
>> + break;
>> + case SIS964_PCALL:
>> + case SIS964_WORD_DATA:
>> + data->word = sis964_read(SMB_BYTE) +
>> + (sis964_read(SMB_BYTE + 1) << 8);
>> + break;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static u32 sis964_func(struct i2c_adapter *adapter)
>> +{
>> + /* SMBus Command protocol supported */
>> + return I2C_FUNC_SMBUS_QUICK | /* Quick command */
>> + I2C_FUNC_SMBUS_BYTE | /* Send/Receive Byte */
>> + I2C_FUNC_SMBUS_BYTE_DATA | /* Read/Write Byte Data */
>> + I2C_FUNC_SMBUS_WORD_DATA | /* Read/Write Word Data */
>> + I2C_FUNC_SMBUS_PROC_CALL | /* Process Call */
>> + I2C_FUNC_SMBUS_BLOCK_DATA; /* Read/Write Block Data */
>> +}
>> +
>> +static int __devinit sis964_setup(struct pci_dev *sis964_dev)
>> +{
>> + unsigned char b;
>> + struct pci_dev *dummy = NULL;
>> + int tmp = 0, i;
>> +
>> + /* check for supported SiS devices */
>> + for (i = 0; supported[i] > 0 && dummy == NULL; i++)
>> + dummy = pci_get_device(PCI_VENDOR_ID_SI, supported[i], dummy);
>> +
>> + if (dummy) {
>> + pci_dev_put(dummy);
>> + } else if (force) {
>> + dev_err(&sis964_dev->dev,
>> + "WARNING: Can't detect SIS964 compatible device, but "
>> + "loading because of force option enabled\n");
>> + } else {
>> + return -ENODEV;
>> + }
>> +
>> +
>> + /*
>> + Enable ACPI first , so we can accsess reg 74-75
>> + in acpi io space and read acpi base addr
>> + */
>> + if (pci_read_config_byte(sis964_dev, SIS964_BIOS_CTL_REG, &b)) {
>> + dev_err(&sis964_dev->dev, "Error: Can't read bios ctl reg\n");
>> + return -ENODEV;
>> + }
>> + /* if ACPI already enabled , do nothing */
>> + if (!(b & 0x80) &&
>> + pci_write_config_byte(sis964_dev, SIS964_BIOS_CTL_REG, b | 0x80)) {
>> + dev_err(&sis964_dev->dev, "Error: Can't enable ACPI\n");
>> + return -ENODEV;
>> + }
>> +
>> + /* Determine the ACPI base address */
>> + if (pci_read_config_word(sis964_dev, SIS964_ACPI_BASE_REG,
>> + &acpi_base)) {
>> + dev_err(&sis964_dev->dev,
>> + "Error: Can't determine ACPI base address\n");
>> + return -ENODEV;
>> + }
>> +
>> + dev_dbg(&sis964_dev->dev, "ACPI base at 0x%04x\n", acpi_base);
>> +
>> + tmp = acpi_check_region(acpi_base + SMB_STS, SIS964_SMB_IOREGION,
>> + sis964_driver.name);
>> + if (tmp) {
>> + acpi_base = 0;
>> + return -ENODEV;
>> + }
>> +
>> + /* Everything is happy, let's grab the memory and set things up. */
>> + if (!request_region(acpi_base + SMB_STS, SIS964_SMB_IOREGION,
>> + sis964_driver.name)) {
>> + dev_err(&sis964_dev->dev,
>> + "SMBus registers 0x%04x-0x%04x already in use!\n",
>> + acpi_base + SMB_STS, acpi_base + SMB_SAA);
>> + acpi_base = 0;
>> + return -ENODEV;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +
>> +static const struct i2c_algorithm smbus_algorithm = {
>> + .smbus_xfer = sis964_access,
>> + .functionality = sis964_func,
>> +};
>> +
>> +static struct i2c_adapter sis964_adapter = {
>> + .owner = THIS_MODULE,
>> + .class = I2C_CLASS_HWMON | I2C_CLASS_SPD,
>> + .algo = &smbus_algorithm,
>> +};
>> +
>> +static DEFINE_PCI_DEVICE_TABLE(sis964_ids) = {
>> + { PCI_DEVICE(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_964) },
>> + { 0 }
>> +};
>> +
>> +MODULE_DEVICE_TABLE(pci, sis964_ids);
>> +
>> +static int __devinit sis964_probe(struct pci_dev *dev,
>> + const struct pci_device_id *id)
>> +{
>> + if (sis964_setup(dev)) {
>> + dev_err(&dev->dev,
>> + "SIS964 comp. bus not detected, module not inserted.\n");
>> + return -ENODEV;
>> + }
>> +
>> + /* set up the sysfs linkage to our parent device */
>> + sis964_adapter.dev.parent = &dev->dev;
>> +
>> + snprintf(sis964_adapter.name, sizeof(sis964_adapter.name),
>> + "SMBus SIS964 adapter at %04xh", acpi_base + SMB_STS);
>> +
>> + return i2c_add_adapter(&sis964_adapter);
>> +}
>> +
>> +static void __devexit sis964_remove(struct pci_dev *dev)
>> +{
>> + dev_dbg(&dev->dev, "sis964_remove");
>> +
>> + if (acpi_base) {
>> + i2c_del_adapter(&sis964_adapter);
>> + release_region(acpi_base + SMB_STS, SIS964_SMB_IOREGION);
>> + acpi_base = 0;
>> + }
>> +}
>> +
>> +
>> +static struct pci_driver sis964_driver = {
>> + .name = "sis964_smbus",
>> + .id_table = sis964_ids,
>> + .probe = sis964_probe,
>> + .remove = __devexit_p(sis964_remove),
>> +};
>> +
>> +module_pci_driver(sis964_driver);
>> +
>> +MODULE_LICENSE("GPL");
>> +MODULE_AUTHOR("Amaury Decrême <amaury.decreme@gmail.com>");
>> +MODULE_DESCRIPTION("SiS964 SMBus driver");
>> diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h
>> index ab741b0..0ffc982 100644
>> --- a/include/linux/pci_ids.h
>> +++ b/include/linux/pci_ids.h
>> @@ -699,6 +699,7 @@
>> #define PCI_DEVICE_ID_SI_961 0x0961
>> #define PCI_DEVICE_ID_SI_962 0x0962
>> #define PCI_DEVICE_ID_SI_963 0x0963
>> +#define PCI_DEVICE_ID_SI_964 0x0964
>
> Please read the comment at the top of this file; I don't think this
> addition qualifies as something that should be added.
>
>> #define PCI_DEVICE_ID_SI_965 0x0965
>> #define PCI_DEVICE_ID_SI_966 0x0966
>> #define PCI_DEVICE_ID_SI_968 0x0968
>> --
>> 1.7.8.6
>>
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH] i2c: sis964: bus driver
2012-07-13 15:36 ` Bjorn Helgaas
2012-07-14 7:53 ` Amaury Decrême
@ 2012-07-15 11:35 ` Jean Delvare
2012-07-18 11:57 ` Amaury Decrême
1 sibling, 1 reply; 5+ messages in thread
From: Jean Delvare @ 2012-07-15 11:35 UTC (permalink / raw)
To: Bjorn Helgaas
Cc: Amaury Decrême, linux-i2c, amalysh, nelson, ben-linux,
w.sang, rob, jeffrey.t.kirsher, akpm, davem, joe, ralf,
dirk.brandewie, jayachandranc, Xiangzhen.Ye, linux-doc,
linux-kernel, linux-pci, suzanne.decreme
Hi Bjorn,
On Fri, 13 Jul 2012 09:36:07 -0600, Bjorn Helgaas wrote:
> On Fri, Jul 13, 2012 at 3:40 AM, Amaury Decrême
> <amaury.decreme@gmail.com> wrote:
> > diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h
> > index ab741b0..0ffc982 100644
> > --- a/include/linux/pci_ids.h
> > +++ b/include/linux/pci_ids.h
> > @@ -699,6 +699,7 @@
> > #define PCI_DEVICE_ID_SI_961 0x0961
> > #define PCI_DEVICE_ID_SI_962 0x0962
> > #define PCI_DEVICE_ID_SI_963 0x0963
> > +#define PCI_DEVICE_ID_SI_964 0x0964
>
> Please read the comment at the top of this file; I don't think this
> addition qualifies as something that should be added.
>
> > #define PCI_DEVICE_ID_SI_965 0x0965
> > #define PCI_DEVICE_ID_SI_966 0x0966
> > #define PCI_DEVICE_ID_SI_968 0x0968
When you reply to a large patch, please limit the quoting to the
relevant portion.
Thanks,
--
Jean Delvare
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH] i2c: sis964: bus driver
2012-07-15 11:35 ` Jean Delvare
@ 2012-07-18 11:57 ` Amaury Decrême
0 siblings, 0 replies; 5+ messages in thread
From: Amaury Decrême @ 2012-07-18 11:57 UTC (permalink / raw)
To: Jean Delvare
Cc: Bjorn Helgaas, linux-i2c, amalysh, nelson, ben-linux, w.sang,
rob, jeffrey.t.kirsher, akpm, davem, joe, ralf, dirk.brandewie,
jayachandranc, Xiangzhen.Ye, linux-doc, linux-kernel, linux-pci,
hi3766691
Hello,
For the v2 of this patch, I will remove the modifications on
include/linux/pci_ids.h
include/linux/pci_ids.h | 1 +
diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h
index ab741b0..0ffc982 100644
--- a/include/linux/pci_ids.h
+++ b/include/linux/pci_ids.h
@@ -699,6 +699,7 @@
#define PCI_DEVICE_ID_SI_961 0x0961
#define PCI_DEVICE_ID_SI_962 0x0962
#define PCI_DEVICE_ID_SI_963 0x0963
+#define PCI_DEVICE_ID_SI_964 0x0964
#define PCI_DEVICE_ID_SI_965 0x0965
#define PCI_DEVICE_ID_SI_966 0x0966
#define PCI_DEVICE_ID_SI_968 0x0968
And will add this to drivers/i2c/busses/i2c-sis964.c directly as
i2c-sis964 will be the only file needing this define
+#define PCI_DEVICE_ID_SI_964 0x0964
Amaury Decrême
^ permalink raw reply related [flat|nested] 5+ messages in thread
end of thread, other threads:[~2012-07-18 11:58 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2012-07-13 9:40 [PATCH] i2c: sis964: bus driver Amaury Decrême
2012-07-13 15:36 ` Bjorn Helgaas
2012-07-14 7:53 ` Amaury Decrême
2012-07-15 11:35 ` Jean Delvare
2012-07-18 11:57 ` Amaury Decrême
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).