summaryrefslogtreecommitdiff
path: root/drivers/i2c/busses/i2c-icy.c
blob: 0d61936dfc0b110bb44d66bf6376c7c1fdb38410 (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
// SPDX-License-Identifier: GPL-2.0
/*
 * I2C driver for stand-alone PCF8584 style adapters on Zorro cards
 *
 * Original ICY documentation can be found on Aminet:
 * https://aminet.net/package/docs/hard/icy
 *
 * There has been a modern community re-print of this design in 2019:
 * https://www.a1k.org/forum/index.php?threads/70106/
 *
 * The card is basically a Philips PCF8584 connected straight to the
 * beginning of the AutoConfig'd address space (register S1 on base+2),
 * with /INT on /INT2 on the Zorro bus.
 *
 * Copyright (c) 2019 Max Staudt <max@enpas.org>
 *
 * This started as a fork of i2c-elektor.c and has evolved since.
 * Thanks go to its authors for providing a base to grow on.
 *
 *
 * IRQ support is currently not implemented.
 *
 * As it turns out, i2c-algo-pcf is really written with i2c-elektor's
 * edge-triggered ISA interrupts in mind, while the Amiga's Zorro bus has
 * level-triggered interrupts. This means that once an interrupt occurs, we
 * have to tell the PCF8584 to shut up immediately, or it will keep the
 * interrupt line busy and cause an IRQ storm.

 * However, because of the PCF8584's host-side protocol, there is no good
 * way to just quieten it without side effects. Rather, we have to perform
 * the next read/write operation straight away, which will reset the /INT
 * pin. This entails re-designing the core of i2c-algo-pcf in the future.
 * For now, we never request an IRQ from the PCF8584, and poll it instead.
 */

#include <linux/delay.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/kernel.h>
#include <linux/module.h>

#include <linux/i2c.h>
#include <linux/i2c-algo-pcf.h>

#include <asm/amigaints.h>
#include <linux/zorro.h>

#include "../algos/i2c-algo-pcf.h"

struct icy_i2c {
	struct i2c_adapter adapter;

	void __iomem *reg_s0;
	void __iomem *reg_s1;
};

/*
 * Functions called by i2c-algo-pcf
 */
static void icy_pcf_setpcf(void *data, int ctl, int val)
{
	struct icy_i2c *i2c = (struct icy_i2c *)data;

	u8 __iomem *address = ctl ? i2c->reg_s1 : i2c->reg_s0;

	z_writeb(val, address);
}

static int icy_pcf_getpcf(void *data, int ctl)
{
	struct icy_i2c *i2c = (struct icy_i2c *)data;

	u8 __iomem *address = ctl ? i2c->reg_s1 : i2c->reg_s0;

	return z_readb(address);
}

static int icy_pcf_getown(void *data)
{
	return 0x55;
}

static int icy_pcf_getclock(void *data)
{
	return 0x1c;
}

static void icy_pcf_waitforpin(void *data)
{
	usleep_range(50, 150);
}

/*
 * Main i2c-icy part
 */
static int icy_probe(struct zorro_dev *z,
		     const struct zorro_device_id *ent)
{
	struct icy_i2c *i2c;
	struct i2c_algo_pcf_data *algo_data;

	i2c = devm_kzalloc(&z->dev, sizeof(*i2c), GFP_KERNEL);
	if (!i2c)
		return -ENOMEM;

	algo_data = devm_kzalloc(&z->dev, sizeof(*algo_data), GFP_KERNEL);
	if (!algo_data)
		return -ENOMEM;

	dev_set_drvdata(&z->dev, i2c);
	i2c->adapter.dev.parent = &z->dev;
	i2c->adapter.owner = THIS_MODULE;
	/* i2c->adapter.algo assigned by i2c_pcf_add_bus() */
	i2c->adapter.algo_data = algo_data;
	strlcpy(i2c->adapter.name, "ICY I2C Zorro adapter",
		sizeof(i2c->adapter.name));

	if (!devm_request_mem_region(&z->dev,
				     z->resource.start,
				     4, i2c->adapter.name))
		return -ENXIO;

	/* Driver private data */
	i2c->reg_s0 = ZTWO_VADDR(z->resource.start);
	i2c->reg_s1 = ZTWO_VADDR(z->resource.start + 2);

	algo_data->data = i2c;
	algo_data->setpcf     = icy_pcf_setpcf;
	algo_data->getpcf     = icy_pcf_getpcf;
	algo_data->getown     = icy_pcf_getown;
	algo_data->getclock   = icy_pcf_getclock;
	algo_data->waitforpin = icy_pcf_waitforpin;

	if (i2c_pcf_add_bus(&i2c->adapter)) {
		dev_err(&z->dev, "i2c_pcf_add_bus() failed\n");
		return -ENXIO;
	}

	dev_info(&z->dev, "ICY I2C controller at %pa, IRQ not implemented\n",
		 &z->resource.start);

	return 0;
}

static void icy_remove(struct zorro_dev *z)
{
	struct icy_i2c *i2c = dev_get_drvdata(&z->dev);

	i2c_del_adapter(&i2c->adapter);
}

static const struct zorro_device_id icy_zorro_tbl[] = {
	{ ZORRO_ID(VMC, 15, 0), },
	{ 0 }
};

MODULE_DEVICE_TABLE(zorro, icy_zorro_tbl);

static struct zorro_driver icy_driver = {
	.name           = "i2c-icy",
	.id_table       = icy_zorro_tbl,
	.probe          = icy_probe,
	.remove         = icy_remove,
};

module_driver(icy_driver,
	      zorro_register_driver,
	      zorro_unregister_driver);

MODULE_AUTHOR("Max Staudt <max@enpas.org>");
MODULE_DESCRIPTION("I2C bus via PCF8584 on ICY Zorro card");
MODULE_LICENSE("GPL v2");