1// Copyright 2018 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4#include <stdint.h>
5#include <string.h>
6#include <threads.h>
7#include <unistd.h>
8
9#include <ddk/binding.h>
10#include <ddk/debug.h>
11#include <ddk/device.h>
12#include <ddk/protocol/i2c-impl.h>
13#include <ddk/protocol/platform-bus.h>
14#include <ddk/protocol/platform-defs.h>
15#include <ddk/protocol/platform-device.h>
16
17#include <fbl/alloc_checker.h>
18#include <fbl/auto_call.h>
19#include <fbl/string_printf.h>
20
21#include <lib/zx/time.h>
22
23#include <zircon/assert.h>
24#include <zircon/types.h>
25
26#include "imx-i2c-regs.h"
27#include "imx-i2c.h"
28
29namespace imx_i2c {
30
31constexpr size_t kMaxTransferSize = UINT16_MAX - 1; // More than enough
32
33uint32_t ImxI2cDevice::I2cImplGetBusCount() {
34    return dev_cnt_;
35}
36
37zx_status_t ImxI2cDevice::I2cImplGetMaxTransferSize(uint32_t bus_id, size_t* out_size) {
38    *out_size = kMaxTransferSize;
39    return ZX_OK;
40}
41
42zx_status_t ImxI2cDevice::I2cImplSetBitRate(uint32_t bus_id, uint32_t bitrate) {
43    // TODO(andresoportus): Support changing frequencies
44    return ZX_ERR_NOT_SUPPORTED;
45}
46
47zx_status_t ImxI2cDevice::I2cImplTransact(uint32_t bus_id, i2c_impl_op_t* ops, size_t count) {
48    if (!atomic_load(&ready_)) {
49        return ZX_ERR_SHOULD_WAIT;
50    }
51    zx_status_t status = ZX_OK;
52    for (size_t i = 0; i < count; ++i) {
53        if (ops[i].address > 0xFF) {
54            return ZX_ERR_NOT_SUPPORTED;
55        }
56        if (ops[i].is_read) {
57            status = Read(static_cast<uint8_t>(ops[i].address), ops[i].buf, ops[i].length,
58                          ops[i].stop);
59        } else {
60            status = Write(static_cast<uint8_t>(ops[i].address), ops[i].buf, ops[i].length,
61                           ops[i].stop);
62        }
63        if (status != ZX_OK) {
64            Reset();
65            return status;
66        }
67    }
68    return ZX_OK;
69}
70
71zx_status_t ImxI2cDevice::WaitFor(Wait type) {
72    zx::time timeout = zx::deadline_after(zx::msec(10));
73    while (zx::clock::get_monotonic() < timeout) {
74        switch (type) {
75        case Wait::kIdle:
76            if (!StatusReg::Get().ReadFrom(mmio_.get()).bus_busy()) {
77                return ZX_OK;
78            }
79            break;
80        case Wait::kBusy:
81            if (StatusReg::Get().ReadFrom(mmio_.get()).bus_busy()) {
82                return ZX_OK;
83            }
84            break;
85        case Wait::kInterruptPending:
86            if (StatusReg::Get().ReadFrom(mmio_.get()).interrupt_pending()) {
87                return ZX_OK;
88            }
89            break;
90        }
91        // TODO(andresoportus): Use interrupts instead of polling
92        zx::nanosleep(zx::deadline_after(zx::usec(10)));
93    }
94    zxlogf(ERROR, "ImxI2cDevice::WaitFor: %s timedout\n", WaitStr(type));
95    ControlReg::Get().ReadFrom(mmio_.get()).Print();
96    StatusReg::Get().ReadFrom(mmio_.get()).Print();
97    return ZX_ERR_TIMED_OUT;
98}
99
100zx_status_t ImxI2cDevice::Start() {
101    ControlReg::Get().ReadFrom(mmio_.get()).set_master(1).set_transmit(1).WriteTo(mmio_.get());
102    return WaitFor(Wait::kBusy);
103}
104
105void ImxI2cDevice::Stop() {
106    ControlReg::Get().ReadFrom(mmio_.get()).set_master(0).set_transmit(0).WriteTo(mmio_.get());
107}
108
109void ImxI2cDevice::Reset() {
110    zxlogf(INFO, "ImxI2cDevice::Reset: reseting...\n");
111    ControlReg::Get().FromValue(0).WriteTo(mmio_.get()); // Implies set_enable(0).
112    StatusReg::Get().FromValue(0).WriteTo(mmio_.get());
113    ControlReg::Get().FromValue(0).set_enable(1).WriteTo(mmio_.get());
114    WaitFor(Wait::kIdle); // No check for error from it
115}
116
117zx_status_t ImxI2cDevice::RxData(uint8_t* buf, size_t length, bool stop) {
118    zx_status_t status;
119    if (length == 0) {
120        return ZX_OK;
121    }
122
123    // Switch to Rx mode
124    auto control = ControlReg::Get().ReadFrom(mmio_.get()).set_transmit(0).set_tx_ack_disable(0);
125    if (length == 1) {
126        // If length is 1 then we need to no ACK (to finish RX) immediately
127        control.set_tx_ack_disable(1);
128    }
129    control.WriteTo(mmio_.get());
130
131    StatusReg::Get().ReadFrom(mmio_.get()).set_interrupt_pending(0).WriteTo(mmio_.get());
132    // Required dummy read, per reference manual:
133    // "If Master Receive mode is required, then I2C_I2CR[MTX] should be toggled and a dummy read
134    // of the I2C_I2DR register must be executed to trigger receive data."
135    DataReg::Get().ReadFrom(mmio_.get()).data();
136
137    for (size_t i = 0; i < length; ++i) {
138
139        // Wait for and check Rx transfer completed
140        status = WaitFor(Wait::kInterruptPending);
141        if (status != ZX_OK) {
142            return status;
143        }
144        if (!StatusReg::Get().ReadFrom(mmio_.get()).transfer_complete()) {
145            return ZX_ERR_IO;
146        }
147        StatusReg::Get().ReadFrom(mmio_.get()).set_interrupt_pending(0).WriteTo(mmio_.get());
148        if (i == length - 2) {
149            // Set TX_ACK_DISABLE two bytes before last
150            ControlReg::Get().ReadFrom(mmio_.get()).set_tx_ack_disable(1).WriteTo(mmio_.get());
151        }
152        if (i == length - 1) {
153            if (stop) {
154                Stop(); // Set STOP one byte before the last
155            }
156        }
157        buf[i] = DataReg::Get().ReadFrom(mmio_.get()).data();
158    }
159    return ZX_OK;
160}
161
162zx_status_t ImxI2cDevice::TxData(const uint8_t* buf, size_t length, bool stop) {
163    for (size_t i = 0; i < length; ++i) {
164        if (i == length - 1 && stop) {
165            Stop(); // Set STOP one byte before the last
166        }
167        StatusReg::Get().ReadFrom(mmio_.get()).set_interrupt_pending(0).WriteTo(mmio_.get());
168        DataReg::Get().FromValue(0).set_data(buf[i]).WriteTo(mmio_.get());
169
170        // Wait for and check Tx transfer completed
171        zx_status_t status = WaitFor(Wait::kInterruptPending);
172        if (status != ZX_OK) {
173            return status;
174        }
175        if (!StatusReg::Get().ReadFrom(mmio_.get()).transfer_complete()) {
176            return ZX_ERR_IO;
177        }
178    }
179    return ZX_OK;
180}
181
182zx_status_t ImxI2cDevice::TxAddress(uint8_t addr, bool is_read) {
183    uint8_t data = static_cast<uint8_t>((addr << 1) | static_cast<uint8_t>(is_read));
184    return TxData(&data, 1, false);
185}
186
187zx_status_t ImxI2cDevice::Read(uint8_t addr, void* buf, size_t len, bool stop) {
188    ControlReg::Get().ReadFrom(mmio_.get()).set_repeat_start(1).WriteTo(mmio_.get());
189    zx_status_t status = TxAddress(addr, true);
190    if (status != ZX_OK) {
191        return status;
192    }
193    return RxData(static_cast<uint8_t*>(buf), len, stop);
194}
195
196zx_status_t ImxI2cDevice::Write(uint8_t addr, const void* buf, size_t len, bool stop) {
197    zx_status_t status = Start();
198    if (status != ZX_OK) {
199        return status;
200    }
201    status = TxAddress(addr, false);
202    if (status != ZX_OK) {
203        return status;
204    }
205    return TxData(static_cast<const uint8_t*>(buf), len, stop);
206}
207
208void ImxI2cDevice::DdkUnbind() {
209    ShutDown();
210    DdkRemove();
211}
212
213void ImxI2cDevice::DdkRelease() {
214    delete this;
215}
216
217int ImxI2cDevice::Thread() {
218    Reset();
219    ready_.store(true);
220//#define TEST_USB_REGS_READ
221#ifdef TEST_USB_REGS_READ
222    for (int i = 0; i < 0xC; i += 2) {
223        uint8_t data_write = static_cast<uint8_t>(i);
224        uint8_t data_read[2];
225        i2c_impl_op_t ops[] = {
226            {.address = 0x50, .buf = &data_write, .length = 1, .is_read = false, .stop = false},
227            {.address = 0x50, .buf = data_read, .length = 2, .is_read = true, .stop = true},
228        };
229        I2cImplTransact(0, ops, 2);
230        zxlogf(INFO, "USB-C Reg:0x%02X Value:0x%02X%02X\n", i, data_read[1], data_read[0]);
231    }
232#endif
233    return 0;
234}
235
236void ImxI2cDevice::ShutDown() {
237    thrd_join(thread_, NULL);
238    io_buffer_release(&regs_iobuff_);
239}
240
241zx_status_t ImxI2cDevice::Bind(int id) {
242    zx_status_t status;
243
244    platform_device_protocol_t pdev;
245    if (device_get_protocol(parent(), ZX_PROTOCOL_PLATFORM_DEV, &pdev) != ZX_OK) {
246        zxlogf(ERROR, "imx_i2c_bind: ZX_PROTOCOL_PLATFORM_DEV not available\n");
247        return ZX_ERR_NOT_SUPPORTED;
248    }
249    platform_bus_protocol_t pbus;
250    if (device_get_protocol(parent(), ZX_PROTOCOL_PLATFORM_BUS, &pbus) != ZX_OK) {
251        zxlogf(ERROR, "imx_i2c_bind: ZX_PROTOCOL_PLATFORM_BUS not available\n");
252        return ZX_ERR_NOT_SUPPORTED;
253    }
254    i2c_impl_protocol_t i2c_proto = {
255        .ops = &ops_,
256        .ctx = this,
257    };
258    pbus_register_protocol(&pbus, ZX_PROTOCOL_I2C_IMPL, &i2c_proto, NULL, NULL);
259
260    status = pdev_map_mmio_buffer(&pdev, id, ZX_CACHE_POLICY_UNCACHED_DEVICE, &regs_iobuff_);
261    if (status != ZX_OK) {
262        zxlogf(ERROR, "ImxI2cDevice::Bind: pdev_map_mmio_buffer failed: %d\n", status);
263        return status;
264    }
265
266    fbl::AllocChecker ac;
267    mmio_ = fbl::make_unique_checked<hwreg::RegisterIo>(&ac, io_buffer_virt(&regs_iobuff_));
268    if (!ac.check()) {
269        zxlogf(ERROR, "ImxI2cDevice::Bind: no memory for RegisterIo\n");
270        io_buffer_release(&regs_iobuff_);
271        return ZX_ERR_NO_MEMORY;
272    }
273
274    int rc = thrd_create_with_name(&thread_,
275                                   [](void* arg) -> int {
276                                       return reinterpret_cast<ImxI2cDevice*>(arg)->Thread();
277                                   },
278                                   this,
279                                   "imxi2c-thread");
280    if (rc != thrd_success) {
281        io_buffer_release(&regs_iobuff_);
282        return ZX_ERR_INTERNAL;
283    }
284
285    auto cleanup = fbl::MakeAutoCall([&]() { ShutDown(); });
286    auto name = fbl::StringPrintf("imx-i2c-%d", id);
287    status = DdkAdd(name.c_str());
288    if (status != ZX_OK) {
289        zxlogf(ERROR, "ImxI2cDevice::Bind: DdkAdd failed: %d\n", status);
290        return status;
291    }
292    cleanup.cancel();
293    return ZX_OK;
294}
295
296} // namespace imx_i2c
297
298extern "C" zx_status_t imx_i2c_bind(void* ctx, zx_device_t* parent) {
299    platform_device_protocol_t pdev;
300    if (device_get_protocol(parent, ZX_PROTOCOL_PLATFORM_DEV, &pdev) != ZX_OK) {
301        zxlogf(ERROR, "imx_i2c_bind: ZX_PROTOCOL_PLATFORM_DEV not available\n");
302        return ZX_ERR_NOT_SUPPORTED;
303    }
304
305    pdev_device_info_t info;
306    zx_status_t status = pdev_get_device_info(&pdev, &info);
307    if (status != ZX_OK) {
308        zxlogf(ERROR, "imx_i2c_bind: pdev_get_device_info failed\n");
309        return ZX_ERR_NOT_SUPPORTED;
310    }
311
312    fbl::AllocChecker ac;
313    for (uint32_t i = 0; i < info.mmio_count; i++) {
314        auto dev = fbl::make_unique_checked<imx_i2c::ImxI2cDevice>(&ac, parent, info.mmio_count);
315        if (!ac.check()) {
316            zxlogf(ERROR, "imx_i2c_bind: ZX_ERR_NO_MEMORY\n");
317            return ZX_ERR_NO_MEMORY;
318        }
319        status = dev->Bind(i);
320        if (status == ZX_OK) {
321            // devmgr is now in charge of the memory for dev
322            __UNUSED auto ptr = dev.release();
323        }
324    }
325    return status;
326}
327