summaryrefslogtreecommitdiff
path: root/drivers/i2c/busses/i2c-ixp4xx.c
blob: f87220be3c87ddc09795e1d77d6dc1eda0e56986 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
/*
 * drivers/i2c/i2c-adap-ixp4xx.c
 *
 * Intel's IXP4xx XScale NPU chipsets (IXP420, 421, 422, 425) do not have
 * an on board I2C controller but provide 16 GPIO pins that are often
 * used to create an I2C bus. This driver provides an i2c_adapter 
 * interface that plugs in under algo_bit and drives the GPIO pins
 * as instructed by the alogorithm driver.
 *
 * Author: Deepak Saxena <dsaxena@plexity.net>
 *
 * Copyright (c) 2003-2004 MontaVista Software Inc.
 *
 * This file is licensed under the terms of the GNU General Public 
 * License version 2. This program is licensed "as is" without any 
 * warranty of any kind, whether express or implied.
 *
 * NOTE: Since different platforms will use different GPIO pins for
 *       I2C, this driver uses an IXP4xx-specific platform_data
 *       pointer to pass the GPIO numbers to the driver. This 
 *       allows us to support all the different IXP4xx platforms
 *       w/o having to put #ifdefs in this driver.
 *
 *       See arch/arm/mach-ixp4xx/ixdp425.c for an example of building a 
 *       device list and filling in the ixp4xx_i2c_pins data structure 
 *       that is passed as the platform_data to this driver.
 */

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/i2c-algo-bit.h>

#include <asm/hardware.h>	/* Pick up IXP4xx-specific bits */

static struct platform_driver ixp4xx_i2c_driver;

static inline int ixp4xx_scl_pin(void *data)
{
	return ((struct ixp4xx_i2c_pins*)data)->scl_pin;
}

static inline int ixp4xx_sda_pin(void *data)
{
	return ((struct ixp4xx_i2c_pins*)data)->sda_pin;
}

static void ixp4xx_bit_setscl(void *data, int val)
{
	gpio_line_set(ixp4xx_scl_pin(data), 0);
	gpio_line_config(ixp4xx_scl_pin(data),
		val ? IXP4XX_GPIO_IN : IXP4XX_GPIO_OUT );
}

static void ixp4xx_bit_setsda(void *data, int val)
{
	gpio_line_set(ixp4xx_sda_pin(data), 0);
	gpio_line_config(ixp4xx_sda_pin(data),
		val ? IXP4XX_GPIO_IN : IXP4XX_GPIO_OUT );
}

static int ixp4xx_bit_getscl(void *data)
{
	int scl;

	gpio_line_config(ixp4xx_scl_pin(data), IXP4XX_GPIO_IN );
	gpio_line_get(ixp4xx_scl_pin(data), &scl);

	return scl;
}	

static int ixp4xx_bit_getsda(void *data)
{
	int sda;

	gpio_line_config(ixp4xx_sda_pin(data), IXP4XX_GPIO_IN );
	gpio_line_get(ixp4xx_sda_pin(data), &sda);

	return sda;
}	

struct ixp4xx_i2c_data {
	struct ixp4xx_i2c_pins *gpio_pins;
	struct i2c_adapter adapter;
	struct i2c_algo_bit_data algo_data;
};

static int ixp4xx_i2c_remove(struct platform_device *plat_dev)
{
	struct ixp4xx_i2c_data *drv_data = platform_get_drvdata(plat_dev);

	platform_set_drvdata(plat_dev, NULL);

	i2c_bit_del_bus(&drv_data->adapter);

	kfree(drv_data);

	return 0;
}

static int ixp4xx_i2c_probe(struct platform_device *plat_dev)
{
	int err;
	struct ixp4xx_i2c_pins *gpio = plat_dev->dev.platform_data;
	struct ixp4xx_i2c_data *drv_data = 
		kzalloc(sizeof(struct ixp4xx_i2c_data), GFP_KERNEL);

	if(!drv_data)
		return -ENOMEM;

	drv_data->gpio_pins = gpio;

	/*
	 * We could make a lot of these structures static, but
	 * certain platforms may have multiple GPIO-based I2C
	 * buses for various device domains, so we need per-device
	 * algo_data->data. 
	 */
	drv_data->algo_data.data = gpio;
	drv_data->algo_data.setsda = ixp4xx_bit_setsda;
	drv_data->algo_data.setscl = ixp4xx_bit_setscl;
	drv_data->algo_data.getsda = ixp4xx_bit_getsda;
	drv_data->algo_data.getscl = ixp4xx_bit_getscl;
	drv_data->algo_data.udelay = 10;
	drv_data->algo_data.mdelay = 10;
	drv_data->algo_data.timeout = 100;

	drv_data->adapter.id = I2C_HW_B_IXP4XX;
	strlcpy(drv_data->adapter.name, ixp4xx_i2c_driver.driver.name,
		I2C_NAME_SIZE);
	drv_data->adapter.algo_data = &drv_data->algo_data;

	drv_data->adapter.dev.parent = &plat_dev->dev;

	gpio_line_config(gpio->scl_pin, IXP4XX_GPIO_IN);
	gpio_line_config(gpio->sda_pin, IXP4XX_GPIO_IN);
	gpio_line_set(gpio->scl_pin, 0);
	gpio_line_set(gpio->sda_pin, 0);

	if ((err = i2c_bit_add_bus(&drv_data->adapter) != 0)) {
		printk(KERN_ERR "ERROR: Could not install %s\n", 
				plat_dev->dev.bus_id);

		kfree(drv_data);
		return err;
	}

	platform_set_drvdata(plat_dev, drv_data);

	return 0;
}

static struct platform_driver ixp4xx_i2c_driver = {
	.probe		= ixp4xx_i2c_probe,
	.remove		= ixp4xx_i2c_remove,
	.driver		= {
		.name	= "IXP4XX-I2C",
		.owner	= THIS_MODULE,
	},
};

static int __init ixp4xx_i2c_init(void)
{
	return platform_driver_register(&ixp4xx_i2c_driver);
}

static void __exit ixp4xx_i2c_exit(void)
{
	platform_driver_unregister(&ixp4xx_i2c_driver);
}

module_init(ixp4xx_i2c_init);
module_exit(ixp4xx_i2c_exit);

MODULE_DESCRIPTION("GPIO-based I2C adapter for IXP4xx systems");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Deepak Saxena <dsaxena@plexity.net>");