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
5#include "aml-ethernet.h"
6#include "aml-regs.h"
7#include <ddk/binding.h>
8#include <ddk/debug.h>
9#include <ddk/driver.h>
10#include <ddk/metadata.h>
11#include <ddk/protocol/ethernet.h>
12#include <ddk/protocol/platform-defs.h>
13#include <ddk/protocol/platform-device.h>
14#include <fbl/auto_call.h>
15#include <fbl/auto_lock.h>
16#include <hw/reg.h>
17#include <soc/aml-s912/s912-hw.h>
18#include <stdio.h>
19#include <string.h>
20#include <zircon/compiler.h>
21
22namespace eth {
23
24#define MCU_I2C_REG_BOOT_EN_WOL 0x21
25#define MCU_I2C_REG_BOOT_EN_WOL_RESET_ENABLE 0x03
26
27template <typename T, typename U>
28static inline T* offset_ptr(U* ptr, size_t offset) {
29    return reinterpret_cast<T*>(reinterpret_cast<uintptr_t>(ptr) + offset);
30}
31
32void AmlEthernet::ResetPhy(void* ctx) {
33    auto& self = *static_cast<AmlEthernet*>(ctx);
34    gpio_write(&self.gpios_[PHY_RESET], 0);
35    zx_nanosleep(zx_deadline_after(ZX_MSEC(100)));
36    gpio_write(&self.gpios_[PHY_RESET], 1);
37    zx_nanosleep(zx_deadline_after(ZX_MSEC(100)));
38}
39
40zx_status_t AmlEthernet::InitPdev(zx_device_t* parent) {
41
42    zx_status_t status = device_get_protocol(parent, ZX_PROTOCOL_PLATFORM_DEV, &pdev_);
43    if (status != ZX_OK) {
44        return status;
45    }
46
47    for (uint32_t i = 0; i < countof(gpios_); i++) {
48        status = pdev_get_protocol(&pdev_, ZX_PROTOCOL_GPIO, i, &gpios_[i]);
49        if (status != ZX_OK) {
50            return status;
51        }
52    }
53
54    // I2c for MCU messages.
55    status = device_get_protocol(parent, ZX_PROTOCOL_I2C, &i2c_);
56    if (status != ZX_OK) {
57        return status;
58    }
59
60    // Map amlogic peripheral control registers.
61    status = pdev_map_mmio_buffer(&pdev_, MMIO_PERIPH, ZX_CACHE_POLICY_UNCACHED_DEVICE,
62                                  &periph_regs_iobuff_);
63    if (status != ZX_OK) {
64        zxlogf(ERROR, "aml-dwmac: could not map periph mmio: %d\n", status);
65        return status;
66    }
67
68    // Map HHI regs (clocks and power domains).
69    status = pdev_map_mmio_buffer(&pdev_, MMIO_HHI, ZX_CACHE_POLICY_UNCACHED_DEVICE,
70                                  &hhi_regs_iobuff_);
71    if (status != ZX_OK) {
72        zxlogf(ERROR, "aml-dwmac: could not map hiu mmio: %d\n", status);
73        return status;
74    }
75
76    return status;
77}
78
79void AmlEthernet::ReleaseBuffers() {
80    io_buffer_release(&periph_regs_iobuff_);
81    io_buffer_release(&hhi_regs_iobuff_);
82}
83
84static void DdkUnbind(void* ctx) {
85    auto& self = *static_cast<AmlEthernet*>(ctx);
86    device_remove(self.device_);
87}
88
89static void DdkRelease(void* ctx) {
90    auto& self = *static_cast<AmlEthernet*>(ctx);
91    self.ReleaseBuffers();
92    delete &self;
93}
94
95static eth_board_protocol_ops_t proto_ops = {
96    .reset_phy = AmlEthernet::ResetPhy,
97};
98
99static zx_protocol_device_t eth_device_ops = []() {
100    zx_protocol_device_t result;
101
102    result.version = DEVICE_OPS_VERSION;
103    result.unbind = &DdkUnbind;
104    result.release = &DdkRelease;
105    return result;
106}();
107
108static device_add_args_t eth_mac_dev_args = []() {
109    device_add_args_t result;
110
111    result.version = DEVICE_ADD_ARGS_VERSION;
112    result.name = "aml-ethernet";
113    result.ops = &eth_device_ops;
114    result.proto_id = ZX_PROTOCOL_ETH_BOARD;
115    result.proto_ops = &proto_ops;
116    return result;
117}();
118
119zx_status_t AmlEthernet::Create(zx_device_t* device) {
120    fbl::AllocChecker ac;
121    auto eth_device = fbl::make_unique_checked<AmlEthernet>(&ac);
122    if (!ac.check()) {
123        return ZX_ERR_NO_MEMORY;
124    }
125
126    zx_status_t status = eth_device->InitPdev(device);
127    if (status != ZX_OK) {
128        return status;
129    }
130
131    // Set reset line to output
132    gpio_config_out(&eth_device->gpios_[PHY_RESET], 0);
133
134    auto cleanup = fbl::MakeAutoCall([&]() { eth_device->ReleaseBuffers(); });
135
136    // Initialize AMLogic peripheral registers associated with dwmac.
137    void* pregs = io_buffer_virt(&eth_device->periph_regs_iobuff_);
138    //Sorry about the magic...rtfm
139    writel(0x1621, offset_ptr<uint32_t>(pregs, PER_ETH_REG0));
140    writel(0x20000, offset_ptr<uint32_t>(pregs, PER_ETH_REG1));
141
142    writel(REG2_ETH_REG2_REVERSED | REG2_INTERNAL_PHY_ID,
143           offset_ptr<uint32_t>(pregs, PER_ETH_REG2));
144
145    writel(REG3_CLK_IN_EN | REG3_ETH_REG3_19_RESVERD |
146               REG3_CFG_PHY_ADDR | REG3_CFG_MODE |
147               REG3_CFG_EN_HIGH | REG3_ETH_REG3_2_RESERVED,
148           offset_ptr<uint32_t>(pregs, PER_ETH_REG3));
149
150    // Enable clocks and power domain for dwmac
151    void* hregs = io_buffer_virt(&eth_device->hhi_regs_iobuff_);
152    set_bitsl(1 << 3, offset_ptr<uint32_t>(hregs, HHI_GCLK_MPEG1));
153    clr_bitsl((1 << 3) | (1 << 2), offset_ptr<uint32_t>(hregs, HHI_MEM_PD_REG0));
154
155    // WOL reset enable to MCU
156    uint8_t write_buf[2] = {MCU_I2C_REG_BOOT_EN_WOL, MCU_I2C_REG_BOOT_EN_WOL_RESET_ENABLE};
157    status = i2c_write_sync(&eth_device->i2c_, write_buf, sizeof(write_buf));
158    if (status) {
159        zxlogf(ERROR, "aml-ethernet: WOL reset enable to MCU failed: %d\n", status);
160        return status;
161    }
162
163    // Populate board specific information
164    eth_dev_metadata_t mac_info;
165    size_t actual;
166    status = device_get_metadata(device, DEVICE_METADATA_PRIVATE, &mac_info,
167                                 sizeof(eth_dev_metadata_t), &actual);
168    if (status != ZX_OK || actual != sizeof(eth_dev_metadata_t)) {
169        zxlogf(ERROR, "aml-ethernet: Could not get MAC metadata %d\n", status);
170        return status;
171    }
172
173    static zx_device_prop_t props[] = {
174        {BIND_PLATFORM_DEV_VID, 0, mac_info.vid},
175        {BIND_PLATFORM_DEV_DID, 0, mac_info.did},
176    };
177
178    eth_mac_dev_args.props = props;
179    eth_mac_dev_args.prop_count = countof(props);
180    eth_mac_dev_args.ctx = eth_device.get();
181
182    status = pdev_device_add(&eth_device->pdev_, 0, &eth_mac_dev_args, &eth_device->device_);
183    if (status != ZX_OK) {
184        zxlogf(ERROR, "aml-ethernet driver failed to get added\n");
185        return status;
186    } else {
187        zxlogf(INFO, "aml-ethernet driver added\n");
188    }
189
190    cleanup.cancel();
191
192    // eth_device intentionally leaked as it is now held by DevMgr
193    __UNUSED auto ptr = eth_device.release();
194
195    return ZX_OK;
196}
197
198} // namespace eth
199
200extern "C" zx_status_t aml_eth_bind(void* ctx, zx_device_t* device) {
201    return eth::AmlEthernet::Create(device);
202}
203