summaryrefslogtreecommitdiffstats
path: root/drivers/i2c/busses/i2c-icy.c
blob: 66c9923fc766593a7364f8171011faa4a5e24f89 (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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
// 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/amigahw.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;
	struct fwnode_handle *ltc2990_fwnode;
	struct i2c_client *ltc2990_client;
};

/*
 * 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 unsigned short const icy_ltc2990_addresses[] = {
	0x4c, 0x4d, 0x4e, 0x4f, I2C_CLIENT_END
};

/*
 * Additional sensors exposed once this property is applied:
 *
 * in1 will be the voltage of the 5V rail, divided by 2.
 * in2 will be the voltage of the 12V rail, divided by 4.
 * temp3 will be measured using a PCB loop next the chip.
 */
static const u32 icy_ltc2990_meas_mode[] = {0, 3};

static const struct property_entry icy_ltc2990_props[] = {
	PROPERTY_ENTRY_U32_ARRAY("lltc,meas-mode", icy_ltc2990_meas_mode),
	{ }
};

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;
	struct fwnode_handle *new_fwnode;
	struct i2c_board_info ltc2990_info = {
		.type		= "ltc2990",
	};

	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);

	/*
	 * The 2019 a1k.org PCBs have an LTC2990 at 0x4c, so start
	 * it automatically once ltc2990 is modprobed.
	 *
	 * in0 is the voltage of the internal 5V power supply.
	 * temp1 is the temperature inside the chip.
	 *
	 * See property_entry above for in1, in2, temp3.
	 */
	new_fwnode = fwnode_create_software_node(icy_ltc2990_props, NULL);
	if (IS_ERR(new_fwnode)) {
		dev_info(&z->dev, "Failed to create fwnode for LTC2990, error: %ld\n",
			 PTR_ERR(new_fwnode));
	} else {
		/*
		 * Store the fwnode so we can destroy it on .remove().
		 * Only store it on success, as fwnode_remove_software_node()
		 * is NULL safe, but not PTR_ERR safe.
		 */
		i2c->ltc2990_fwnode = new_fwnode;
		ltc2990_info.fwnode = new_fwnode;

		i2c->ltc2990_client =
			i2c_new_scanned_device(&i2c->adapter,
					       &ltc2990_info,
					       icy_ltc2990_addresses,
					       NULL);
	}

	return 0;
}

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

	i2c_unregister_device(i2c->ltc2990_client);
	fwnode_remove_software_node(i2c->ltc2990_fwnode);

	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");