cregit-Linux how code gets into the kernel

Release 4.7 drivers/i2c/busses/scx200_acb.c

/*
    Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com>

    National Semiconductor SCx200 ACCESS.bus support
    Also supports the AMD CS5535 and AMD CS5536

    Based on i2c-keywest.c which is:
        Copyright (c) 2001 Benjamin Herrenschmidt <benh@kernel.crashing.org>
        Copyright (c) 2000 Philip Edelbrock <phil@stimpy.netroedge.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.
*/


#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/module.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/io.h>

#include <linux/scx200.h>

MODULE_AUTHOR("Christer Weinigel <wingel@nano-system.com>");
MODULE_DESCRIPTION("NatSemi SCx200 ACCESS.bus Driver");
MODULE_ALIAS("platform:cs5535-smb");
MODULE_LICENSE("GPL");


#define MAX_DEVICES 4

static int base[MAX_DEVICES] = { 0x820, 0x840 };
module_param_array(base, int, NULL, 0);
MODULE_PARM_DESC(base, "Base addresses for the ACCESS.bus controllers");


#define POLL_TIMEOUT	(HZ/5)


enum scx200_acb_state {
	
state_idle,
	
state_address,
	
state_command,
	
state_repeat_start,
	
state_quick,
	
state_read,
	
state_write,
};


static const char *scx200_acb_state_name[] = {
	"idle",
	"address",
	"command",
	"repeat_start",
	"quick",
	"read",
	"write",
};

/* Physical interface */

struct scx200_acb_iface {
	
struct scx200_acb_iface *next;
	
struct i2c_adapter adapter;
	
unsigned base;
	
struct mutex mutex;

	/* State machine data */
	
enum scx200_acb_state state;
	
int result;
	
u8 address_byte;
	
u8 command;
	
u8 *ptr;
	
char needs_reset;
	
unsigned len;
};

/* Register Definitions */

#define ACBSDA		(iface->base + 0)

#define ACBST		(iface->base + 1)

#define    ACBST_SDAST		0x40 
/* SDA Status */

#define    ACBST_BER		0x20

#define    ACBST_NEGACK		0x10 
/* Negative Acknowledge */

#define    ACBST_STASTR		0x08 
/* Stall After Start */

#define    ACBST_MASTER		0x02

#define ACBCST		(iface->base + 2)

#define    ACBCST_BB		0x02

#define ACBCTL1		(iface->base + 3)

#define    ACBCTL1_STASTRE	0x80

#define    ACBCTL1_NMINTE	0x40

#define    ACBCTL1_ACK		0x10

#define    ACBCTL1_STOP		0x02

#define    ACBCTL1_START	0x01

#define ACBADDR		(iface->base + 4)

#define ACBCTL2		(iface->base + 5)

#define    ACBCTL2_ENABLE	0x01

/************************************************************************/


static void scx200_acb_machine(struct scx200_acb_iface *iface, u8 status) { const char *errmsg; dev_dbg(&iface->adapter.dev, "state %s, status = 0x%02x\n", scx200_acb_state_name[iface->state], status); if (status & ACBST_BER) { errmsg = "bus error"; goto error; } if (!(status & ACBST_MASTER)) { errmsg = "not master"; goto error; } if (status & ACBST_NEGACK) { dev_dbg(&iface->adapter.dev, "negative ack in state %s\n", scx200_acb_state_name[iface->state]); iface->state = state_idle; iface->result = -ENXIO; outb(inb(ACBCTL1) | ACBCTL1_STOP, ACBCTL1); outb(ACBST_STASTR | ACBST_NEGACK, ACBST); /* Reset the status register */ outb(0, ACBST); return; } switch (iface->state) { case state_idle: dev_warn(&iface->adapter.dev, "interrupt in idle state\n"); break; case state_address: /* Do a pointer write first */ outb(iface->address_byte & ~1, ACBSDA); iface->state = state_command; break; case state_command: outb(iface->command, ACBSDA); if (iface->address_byte & 1) iface->state = state_repeat_start; else iface->state = state_write; break; case state_repeat_start: outb(inb(ACBCTL1) | ACBCTL1_START, ACBCTL1); /* fallthrough */ case state_quick: if (iface->address_byte & 1) { if (iface->len == 1) outb(inb(ACBCTL1) | ACBCTL1_ACK, ACBCTL1); else outb(inb(ACBCTL1) & ~ACBCTL1_ACK, ACBCTL1); outb(iface->address_byte, ACBSDA); iface->state = state_read; } else { outb(iface->address_byte, ACBSDA); iface->state = state_write; } break; case state_read: /* Set ACK if _next_ byte will be the last one */ if (iface->len == 2) outb(inb(ACBCTL1) | ACBCTL1_ACK, ACBCTL1); else outb(inb(ACBCTL1) & ~ACBCTL1_ACK, ACBCTL1); if (iface->len == 1) { iface->result = 0; iface->state = state_idle; outb(inb(ACBCTL1) | ACBCTL1_STOP, ACBCTL1); } *iface->ptr++ = inb(ACBSDA); --iface->len; break; case state_write: if (iface->len == 0) { iface->result = 0; iface->state = state_idle; outb(inb(ACBCTL1) | ACBCTL1_STOP, ACBCTL1); break; } outb(*iface->ptr++, ACBSDA); --iface->len; break; } return; error: dev_err(&iface->adapter.dev, "%s in state %s (addr=0x%02x, len=%d, status=0x%02x)\n", errmsg, scx200_acb_state_name[iface->state], iface->address_byte, iface->len, status); iface->state = state_idle; iface->result = -EIO; iface->needs_reset = 1; }

Contributors

PersonTokensPropCommitsCommitProp
christer weinigelchrister weinigel39175.48%112.50%
ben gardnerben gardner6412.36%225.00%
greg kroah-hartmangreg kroah-hartman254.83%225.00%
thomas andrewsthomas andrews193.67%112.50%
willy tarreauwilly tarreau112.12%112.50%
jordan crousejordan crouse81.54%112.50%
Total518100.00%8100.00%


static void scx200_acb_poll(struct scx200_acb_iface *iface) { u8 status; unsigned long timeout; timeout = jiffies + POLL_TIMEOUT; while (1) { status = inb(ACBST); /* Reset the status register to avoid the hang */ outb(0, ACBST); if ((status & (ACBST_SDAST|ACBST_BER|ACBST_NEGACK)) != 0) { scx200_acb_machine(iface, status); return; } if (time_after(jiffies, timeout)) break; cpu_relax(); cond_resched(); } dev_err(&iface->adapter.dev, "timeout in state %s\n", scx200_acb_state_name[iface->state]); iface->state = state_idle; iface->result = -EIO; iface->needs_reset = 1; }

Contributors

PersonTokensPropCommitsCommitProp
christer weinigelchrister weinigel6753.60%120.00%
ben gardnerben gardner3528.00%240.00%
david woodhousedavid woodhouse1512.00%120.00%
jordan crousejordan crouse86.40%120.00%
Total125100.00%5100.00%


static void scx200_acb_reset(struct scx200_acb_iface *iface) { /* Disable the ACCESS.bus device and Configure the SCL frequency: 16 clock cycles */ outb(0x70, ACBCTL2); /* Polling mode */ outb(0, ACBCTL1); /* Disable slave address */ outb(0, ACBADDR); /* Enable the ACCESS.bus device */ outb(inb(ACBCTL2) | ACBCTL2_ENABLE, ACBCTL2); /* Free STALL after START */ outb(inb(ACBCTL1) & ~(ACBCTL1_STASTRE | ACBCTL1_NMINTE), ACBCTL1); /* Send a STOP */ outb(inb(ACBCTL1) | ACBCTL1_STOP, ACBCTL1); /* Clear BER, NEGACK and STASTR bits */ outb(ACBST_BER | ACBST_NEGACK | ACBST_STASTR, ACBST); /* Clear BB bit */ outb(inb(ACBCST) | ACBCST_BB, ACBCST); }

Contributors

PersonTokensPropCommitsCommitProp
christer weinigelchrister weinigel10399.04%150.00%
ben gardnerben gardner10.96%150.00%
Total104100.00%2100.00%


static s32 scx200_acb_smbus_xfer(struct i2c_adapter *adapter, u16 address, unsigned short flags, char rw, u8 command, int size, union i2c_smbus_data *data) { struct scx200_acb_iface *iface = i2c_get_adapdata(adapter); int len; u8 *buffer; u16 cur_word; int rc; switch (size) { case I2C_SMBUS_QUICK: len = 0; buffer = NULL; break; case I2C_SMBUS_BYTE: len = 1; buffer = rw ? &data->byte : &command; break; case I2C_SMBUS_BYTE_DATA: len = 1; buffer = &data->byte; break; case I2C_SMBUS_WORD_DATA: len = 2; cur_word = cpu_to_le16(data->word); buffer = (u8 *)&cur_word; break; case I2C_SMBUS_I2C_BLOCK_DATA: len = data->block[0]; if (len == 0 || len > I2C_SMBUS_BLOCK_MAX) return -EINVAL; buffer = &data->block[1]; break; default: return -EINVAL; } dev_dbg(&adapter->dev, "size=%d, address=0x%x, command=0x%x, len=%d, read=%d\n", size, address, command, len, rw); if (!len && rw == I2C_SMBUS_READ) { dev_dbg(&adapter->dev, "zero length read\n"); return -EINVAL; } mutex_lock(&iface->mutex); iface->address_byte = (address << 1) | rw; iface->command = command; iface->ptr = buffer; iface->len = len; iface->result = -EINVAL; iface->needs_reset = 0; outb(inb(ACBCTL1) | ACBCTL1_START, ACBCTL1); if (size == I2C_SMBUS_QUICK || size == I2C_SMBUS_BYTE) iface->state = state_quick; else iface->state = state_address; while (iface->state != state_idle) scx200_acb_poll(iface); if (iface->needs_reset) scx200_acb_reset(iface); rc = iface->result; mutex_unlock(&iface->mutex); if (rc == 0 && size == I2C_SMBUS_WORD_DATA && rw == I2C_SMBUS_READ) data->word = le16_to_cpu(cur_word); #ifdef DEBUG dev_dbg(&adapter->dev, "transfer done, result: %d", rc); if (buffer) { int i; printk(" data:"); for (i = 0; i < len; ++i) printk(" %02x", buffer[i]); } printk("\n"); #endif return rc; }

Contributors

PersonTokensPropCommitsCommitProp
christer weinigelchrister weinigel37887.91%111.11%
jean delvarejean delvare214.88%333.33%
ben gardnerben gardner204.65%222.22%
greg kroah-hartmangreg kroah-hartman112.56%333.33%
Total430100.00%9100.00%


static u32 scx200_acb_func(struct i2c_adapter *adapter) { return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_I2C_BLOCK; }

Contributors

PersonTokensPropCommitsCommitProp
christer weinigelchrister weinigel2195.45%150.00%
jean delvarejean delvare14.55%150.00%
Total22100.00%2100.00%

/* For now, we only handle combined mode (smbus) */ static const struct i2c_algorithm scx200_acb_algorithm = { .smbus_xfer = scx200_acb_smbus_xfer, .functionality = scx200_acb_func, }; static struct scx200_acb_iface *scx200_acb_list; static DEFINE_MUTEX(scx200_acb_list_mutex);
static int scx200_acb_probe(struct scx200_acb_iface *iface) { u8 val; /* Disable the ACCESS.bus device and Configure the SCL frequency: 16 clock cycles */ outb(0x70, ACBCTL2); if (inb(ACBCTL2) != 0x70) { pr_debug("ACBCTL2 readback failed\n"); return -ENXIO; } outb(inb(ACBCTL1) | ACBCTL1_NMINTE, ACBCTL1); val = inb(ACBCTL1); if (val) { pr_debug("disabled, but ACBCTL1=0x%02x\n", val); return -ENXIO; } outb(inb(ACBCTL2) | ACBCTL2_ENABLE, ACBCTL2); outb(inb(ACBCTL1) | ACBCTL1_NMINTE, ACBCTL1); val = inb(ACBCTL1); if ((val & ACBCTL1_NMINTE) != ACBCTL1_NMINTE) { pr_debug("enabled, but NMINTE won't be set, ACBCTL1=0x%02x\n", val); return -ENXIO; } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
christer weinigelchrister weinigel12794.07%120.00%
ben gardnerben gardner42.96%240.00%
jim cromiejim cromie32.22%120.00%
adrian bunkadrian bunk10.74%120.00%
Total135100.00%5100.00%


static struct scx200_acb_iface *scx200_create_iface(const char *text, struct device *dev, int index) { struct scx200_acb_iface *iface; struct i2c_adapter *adapter; iface = kzalloc(sizeof(*iface), GFP_KERNEL); if (!iface) return NULL; adapter = &iface->adapter; i2c_set_adapdata(adapter, iface); snprintf(adapter->name, sizeof(adapter->name), "%s ACB%d", text, index); adapter->owner = THIS_MODULE; adapter->algo = &scx200_acb_algorithm; adapter->class = I2C_CLASS_HWMON | I2C_CLASS_SPD; adapter->dev.parent = dev; mutex_init(&iface->mutex); return iface; }

Contributors

PersonTokensPropCommitsCommitProp
christer weinigelchrister weinigel5342.06%17.69%
bill pembertonbill pemberton2519.84%17.69%
jean delvarejean delvare1814.29%430.77%
jordan crousejordan crouse86.35%17.69%
david brownelldavid brownell64.76%17.69%
greg kroah-hartmangreg kroah-hartman64.76%215.38%
christoph hellwigchristoph hellwig64.76%17.69%
ben gardnerben gardner32.38%17.69%
deepak saxenadeepak saxena10.79%17.69%
Total126100.00%13100.00%


static int scx200_acb_create(struct scx200_acb_iface *iface) { struct i2c_adapter *adapter; int rc; adapter = &iface->adapter; rc = scx200_acb_probe(iface); if (rc) { pr_warn("probe failed\n"); return rc; } scx200_acb_reset(iface); if (i2c_add_adapter(adapter) < 0) { pr_err("failed to register\n"); return -ENODEV; } if (!adapter->dev.parent) { /* If there's no dev, we're tracking (ISA) ifaces manually */ mutex_lock(&scx200_acb_list_mutex); iface->next = scx200_acb_list; scx200_acb_list = iface; mutex_unlock(&scx200_acb_list_mutex); } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
christer weinigelchrister weinigel5752.29%114.29%
jordan crousejordan crouse2321.10%114.29%
andres salomonandres salomon1211.01%114.29%
ben gardnerben gardner87.34%114.29%
jim cromiejim cromie65.50%114.29%
matthias kaehlckematthias kaehlcke21.83%114.29%
bill pembertonbill pemberton10.92%114.29%
Total109100.00%7100.00%


static struct scx200_acb_iface *scx200_create_dev(const char *text, unsigned long base, int index, struct device *dev) { struct scx200_acb_iface *iface; int rc; iface = scx200_create_iface(text, dev, index); if (iface == NULL) return NULL; if (!request_region(base, 8, iface->adapter.name)) { pr_err("can't allocate io 0x%lx-0x%lx\n", base, base + 8 - 1); goto errout_free; } iface->base = base; rc = scx200_acb_create(iface); if (rc == 0) return iface; release_region(base, 8); errout_free: kfree(iface); return NULL; }

Contributors

PersonTokensPropCommitsCommitProp
jordan crousejordan crouse6753.17%116.67%
andres salomonandres salomon2822.22%116.67%
ben gardnerben gardner2015.87%116.67%
jim cromiejim cromie75.56%116.67%
jean delvarejean delvare21.59%116.67%
jeff garzikjeff garzik21.59%116.67%
Total126100.00%6100.00%


static int scx200_probe(struct platform_device *pdev) { struct scx200_acb_iface *iface; struct resource *res; res = platform_get_resource(pdev, IORESOURCE_IO, 0); if (!res) { dev_err(&pdev->dev, "can't fetch device resource info\n"); return -ENODEV; } iface = scx200_create_dev("CS5535", res->start, 0, &pdev->dev); if (!iface) return -EIO; dev_info(&pdev->dev, "SCx200 device '%s' registered\n", iface->adapter.name); platform_set_drvdata(pdev, iface); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
andres salomonandres salomon6561.32%120.00%
jordan crousejordan crouse3734.91%120.00%
christer weinigelchrister weinigel21.89%120.00%
adrian bunkadrian bunk10.94%120.00%
jean delvarejean delvare10.94%120.00%
Total106100.00%5100.00%


static void scx200_cleanup_iface(struct scx200_acb_iface *iface) { i2c_del_adapter(&iface->adapter); release_region(iface->base, 8); kfree(iface); }

Contributors

PersonTokensPropCommitsCommitProp
jordan crousejordan crouse1854.55%150.00%
andres salomonandres salomon1545.45%150.00%
Total33100.00%2100.00%


static int scx200_remove(struct platform_device *pdev) { struct scx200_acb_iface *iface; iface = platform_get_drvdata(pdev); scx200_cleanup_iface(iface); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
andres salomonandres salomon2683.87%150.00%
jordan crousejordan crouse516.13%150.00%
Total31100.00%2100.00%

static struct platform_driver scx200_pci_driver = { .driver = { .name = "cs5535-smb", }, .probe = scx200_probe, .remove = scx200_remove, }; static const struct pci_device_id scx200_isa[] = { { PCI_DEVICE(PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_SCx200_BRIDGE) }, { PCI_DEVICE(PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_SC1100_BRIDGE) }, { 0, } };
static __init void scx200_scan_isa(void) { int i; if (!pci_dev_present(scx200_isa)) return; for (i = 0; i < MAX_DEVICES; ++i) { if (base[i] == 0) continue; /* XXX: should we care about failures? */ scx200_create_dev("SCx200", base[i], i, NULL); } }

Contributors

PersonTokensPropCommitsCommitProp
christer weinigelchrister weinigel3963.93%120.00%
andres salomonandres salomon1321.31%120.00%
jordan crousejordan crouse58.20%120.00%
ben gardnerben gardner23.28%120.00%
jean delvarejean delvare23.28%120.00%
Total61100.00%5100.00%


static int __init scx200_acb_init(void) { pr_debug("NatSemi SCx200 ACCESS.bus Driver\n"); /* First scan for ISA-based devices */ scx200_scan_isa(); /* XXX: should we care about errors? */ /* If at least one bus was created, init must succeed */ if (scx200_acb_list) return 0; /* No ISA devices; register the platform driver for PCI-based devices */ return platform_driver_register(&scx200_pci_driver); }

Contributors

PersonTokensPropCommitsCommitProp
jordan crousejordan crouse1028.57%114.29%
jean delvarejean delvare822.86%114.29%
andres salomonandres salomon822.86%114.29%
ben gardnerben gardner411.43%114.29%
christer weinigelchrister weinigel38.57%114.29%
harvey yangharvey yang12.86%114.29%
jim cromiejim cromie12.86%114.29%
Total35100.00%7100.00%


static void __exit scx200_acb_cleanup(void) { struct scx200_acb_iface *iface; platform_driver_unregister(&scx200_pci_driver); mutex_lock(&scx200_acb_list_mutex); while ((iface = scx200_acb_list) != NULL) { scx200_acb_list = iface->next; mutex_unlock(&scx200_acb_list_mutex); scx200_cleanup_iface(iface); mutex_lock(&scx200_acb_list_mutex); } mutex_unlock(&scx200_acb_list_mutex); }

Contributors

PersonTokensPropCommitsCommitProp
christer weinigelchrister weinigel4059.70%120.00%
ben gardnerben gardner1623.88%120.00%
andres salomonandres salomon68.96%120.00%
matthias kaehlckematthias kaehlcke45.97%120.00%
harvey yangharvey yang11.49%120.00%
Total67100.00%5100.00%

module_init(scx200_acb_init); module_exit(scx200_acb_cleanup);

Overall Contributors

PersonTokensPropCommitsCommitProp
christer weinigelchrister weinigel154064.33%12.17%
jordan crousejordan crouse2108.77%24.35%
andres salomonandres salomon2098.73%12.17%
ben gardnerben gardner1837.64%613.04%
jean delvarejean delvare592.46%919.57%
greg kroah-hartmangreg kroah-hartman471.96%510.87%
bill pembertonbill pemberton261.09%12.17%
jim cromiejim cromie241.00%12.17%
thomas andrewsthomas andrews190.79%12.17%
david woodhousedavid woodhouse150.63%12.17%
willy tarreauwilly tarreau110.46%12.17%
matthias kaehlckematthias kaehlcke70.29%12.17%
jingoo hanjingoo han60.25%12.17%
christoph hellwigchristoph hellwig60.25%12.17%
david brownelldavid brownell60.25%12.17%
art haasart haas40.17%12.17%
tejun heotejun heo30.13%12.17%
nishanth aravamudannishanth aravamudan30.13%12.17%
adrian bunkadrian bunk30.13%24.35%
harvey yangharvey yang30.13%12.17%
dzianis kahanovichdzianis kahanovich20.08%12.17%
jeff garzikjeff garzik20.08%12.17%
andrew mortonandrew morton20.08%12.17%
wolfram sangwolfram sang10.04%12.17%
h hartley sweetenh hartley sweeten10.04%12.17%
rusty russellrusty russell10.04%12.17%
deepak saxenadeepak saxena10.04%12.17%
axel linaxel lin0.00%00.00%
Total2394100.00%46100.00%
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
{% endraw %}