1/*
2 * Copyright 2022, Haiku, Inc.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include "ocores_i2c.h"
8#include <bus/FDT.h>
9
10#include <AutoDeleterDrivers.h>
11
12#include <string.h>
13#include <new>
14
15
16int32
17OcoresI2c::InterruptReceived(void* arg)
18{
19	return static_cast<OcoresI2c*>(arg)->InterruptReceivedInt();
20}
21
22
23int32
24OcoresI2c::InterruptReceivedInt()
25{
26	// TODO: implement interrupt handling, use polling for now
27	return B_HANDLED_INTERRUPT;
28}
29
30
31status_t
32OcoresI2c::WaitCompletion()
33{
34	while (!fRegs->status.interrupt) {}
35	return B_OK;
36}
37
38
39status_t
40OcoresI2c::WriteByte(OcoresI2cRegsCommand cmd, uint8 val)
41{
42	cmd.intAck = true;
43	cmd.write = true;
44	//dprintf("OcoresI2c::WriteByte(cmd: %#02x, val: %#02x)\n", cmd.val, val);
45	fRegs->data = val;
46	fRegs->command.val = cmd.val;
47	CHECK_RET(WaitCompletion());
48	return B_OK;
49}
50
51
52status_t
53OcoresI2c::ReadByte(OcoresI2cRegsCommand cmd, uint8& val)
54{
55	cmd.intAck = true;
56	cmd.read = true;
57	cmd.nack = cmd.stop;
58	fRegs->command.val = cmd.val;
59	CHECK_RET(WaitCompletion());
60	val = fRegs->data;
61	//dprintf("OcoresI2c::ReadByte(cmd: %#02x, val: %#02x)\n", cmd.val, val);
62	return B_OK;
63}
64
65
66status_t
67OcoresI2c::WriteAddress(i2c_addr adr, bool isRead)
68{
69	// TODO: 10 bit address support
70	//dprintf("OcoresI2c::WriteAddress(adr: %#04x, isRead: %d)\n", adr, isRead);
71	uint8 val = OcoresI2cRegsAddress7{.read = isRead, .address = (uint8)adr}.val;
72	CHECK_RET(WriteByte({.start = true}, val));
73	return B_OK;
74}
75
76
77//#pragma mark - driver
78
79float
80OcoresI2c::SupportsDevice(device_node* parent)
81{
82	const char* bus;
83	status_t status = gDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false);
84	if (status < B_OK)
85		return -1.0f;
86
87	if (strcmp(bus, "fdt") != 0)
88		return 0.0f;
89
90	const char* compatible;
91	status = gDeviceManager->get_attr_string(parent, "fdt/compatible", &compatible, false);
92	if (status < B_OK)
93		return -1.0f;
94
95	if (strcmp(compatible, "opencores,i2c-ocores") != 0
96		&& strcmp(compatible, "sifive,fu740-c000-i2c") != 0
97		&& strcmp(compatible, "sifive,i2c0") != 0) {
98		return 0.0f;
99	}
100
101	return 1.0f;
102}
103
104
105status_t
106OcoresI2c::RegisterDevice(device_node* parent)
107{
108	device_attr attrs[] = {
109		{ B_DEVICE_PRETTY_NAME, B_STRING_TYPE, {string: "Opencores I2C Controller"} },
110		{ B_DEVICE_FIXED_CHILD, B_STRING_TYPE, {string: I2C_FOR_CONTROLLER_MODULE_NAME} },
111		{}
112	};
113
114	return gDeviceManager->register_node(parent, OCORES_I2C_DRIVER_MODULE_NAME, attrs, NULL, NULL);
115}
116
117
118status_t
119OcoresI2c::InitDriver(device_node* node, OcoresI2c*& outDriver)
120{
121	ObjectDeleter<OcoresI2c> driver(new(std::nothrow) OcoresI2c());
122	if (!driver.IsSet())
123		return B_NO_MEMORY;
124
125	CHECK_RET(driver->InitDriverInt(node));
126	outDriver = driver.Detach();
127	return B_OK;
128}
129
130
131status_t
132OcoresI2c::InitDriverInt(device_node* node)
133{
134	fNode = node;
135	dprintf("+OcoresI2c::InitDriver()\n");
136
137	DeviceNodePutter<&gDeviceManager> parent(gDeviceManager->get_parent_node(node));
138
139	const char* bus;
140	CHECK_RET(gDeviceManager->get_attr_string(parent.Get(), B_DEVICE_BUS, &bus, false));
141	if (strcmp(bus, "fdt") != 0)
142		return B_ERROR;
143
144	fdt_device_module_info *parentModule;
145	fdt_device* parentDev;
146	CHECK_RET(gDeviceManager->get_driver(parent.Get(),
147		(driver_module_info**)&parentModule, (void**)&parentDev));
148
149	uint64 regs = 0;
150	uint64 regsLen = 0;
151	if (!parentModule->get_reg(parentDev, 0, &regs, &regsLen))
152		return B_ERROR;
153
154	fRegsArea.SetTo(map_physical_memory("Ocores i2c MMIO", regs, regsLen, B_ANY_KERNEL_ADDRESS,
155		B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA, (void**)&fRegs));
156	if (!fRegsArea.IsSet())
157		return fRegsArea.Get();
158
159	uint64 irq;
160	if (!parentModule->get_interrupt(parentDev, 0, NULL, &irq))
161		return B_ERROR;
162	fIrqVector = irq; // TODO: take interrupt controller into account
163
164	// TODO: enable when implement interrupt handling
165	if (false)
166		install_io_interrupt_handler(fIrqVector, InterruptReceived, this, 0);
167
168	dprintf("-OcoresI2c::InitDriver()\n");
169	return B_OK;
170}
171
172
173void
174OcoresI2c::UninitDriver()
175{
176	if (false)
177		remove_io_interrupt_handler(fIrqVector, InterruptReceived, this);
178	delete this;
179}
180
181
182//#pragma mark - i2c controller
183
184void
185OcoresI2c::SetI2cBus(i2c_bus bus)
186{
187	dprintf("OcoresI2c::SetI2cBus()\n");
188	fBus = bus;
189}
190
191
192status_t
193OcoresI2c::ExecCommand(i2c_op op,
194	i2c_addr slaveAddress, const uint8 *cmdBuffer, size_t cmdLength,
195	uint8* dataBuffer, size_t dataLength)
196{
197	//dprintf("OcoresI2c::ExecCommand()\n");
198	(void)op;
199	if (cmdLength > 0) {
200		CHECK_RET(WriteAddress(slaveAddress, false));
201		do {
202			if (fRegs->status.nackReceived) {
203				fRegs->command.val = OcoresI2cRegsCommand{
204					.intAck = true,
205					.stop = true
206				}.val;
207				return B_ERROR;
208			}
209			cmdLength--;
210			CHECK_RET(WriteByte({.stop = cmdLength == 0 && dataLength == 0}, *cmdBuffer++));
211		} while (cmdLength > 0);
212	}
213	if (dataLength > 0) {
214		CHECK_RET(WriteAddress(slaveAddress, true));
215		do {
216			dataLength--;
217			CHECK_RET(ReadByte({.stop = dataLength == 0}, *dataBuffer++));
218		} while (dataLength > 0);
219	}
220	return B_OK;
221}
222
223
224status_t
225OcoresI2c::AcquireBus()
226{
227	return mutex_lock(&fLock);
228}
229
230
231void
232OcoresI2c::ReleaseBus()
233{
234	mutex_unlock(&fLock);
235}
236