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/platform-defs.h>
12 #include <ddk/protocol/i2c-lib.h>
13 #include <ddk/protocol/ethernet.h>
14 #include <ddk/protocol/platform/device.h>
15 #include <ddk/protocol/platform-device-lib.h>
16 #include <fbl/auto_call.h>
17 #include <fbl/auto_lock.h>
18 #include <fbl/unique_ptr.h>
19 #include <hw/reg.h>
20 #include <soc/aml-s912/s912-hw.h>
21 #include <stdio.h>
22 #include <string.h>
23 #include <zircon/compiler.h>
24 
25 namespace eth {
26 
27 #define MCU_I2C_REG_BOOT_EN_WOL 0x21
28 #define MCU_I2C_REG_BOOT_EN_WOL_RESET_ENABLE 0x03
29 
EthBoardResetPhy()30 void AmlEthernet::EthBoardResetPhy() {
31     gpios_[PHY_RESET]->Write(0);
32     zx_nanosleep(zx_deadline_after(ZX_MSEC(100)));
33     gpios_[PHY_RESET]->Write(1);
34     zx_nanosleep(zx_deadline_after(ZX_MSEC(100)));
35 }
36 
InitPdev()37 zx_status_t AmlEthernet::InitPdev() {
38     if (!pdev_.is_valid()) {
39         return ZX_ERR_NO_RESOURCES;
40     }
41 
42     zx_status_t status;
43     for (uint32_t i = 0; i < countof(gpios_); i++) {
44         gpios_[i] = pdev_.GetGpio(i);
45         if (!gpios_[i]) {
46             return ZX_ERR_NO_RESOURCES;
47         }
48     }
49 
50     // I2c for MCU messages.
51     i2c_ = pdev_.GetI2c(0);
52     if (!i2c_) {
53         return ZX_ERR_NO_RESOURCES;
54     }
55 
56     // Map amlogic peripheral control registers.
57     status = pdev_.MapMmio(MMIO_PERIPH, &periph_mmio_);
58     if (status != ZX_OK) {
59         zxlogf(ERROR, "aml-dwmac: could not map periph mmio: %d\n", status);
60         return status;
61     }
62 
63     // Map HHI regs (clocks and power domains).
64     status = pdev_.MapMmio(MMIO_HHI, &hhi_mmio_);
65     if (status != ZX_OK) {
66         zxlogf(ERROR, "aml-dwmac: could not map hiu mmio: %d\n", status);
67         return status;
68     }
69 
70     return status;
71 }
72 
Bind()73 zx_status_t AmlEthernet::Bind() {
74     // Set reset line to output
75     gpios_[PHY_RESET]->ConfigOut(0);
76 
77     // Initialize AMLogic peripheral registers associated with dwmac.
78     //Sorry about the magic...rtfm
79     periph_mmio_->Write32(0x1621, PER_ETH_REG0);
80     periph_mmio_->Write32(0x20000, PER_ETH_REG1);
81 
82     periph_mmio_->Write32(REG2_ETH_REG2_REVERSED | REG2_INTERNAL_PHY_ID, PER_ETH_REG2);
83 
84     periph_mmio_->Write32(REG3_CLK_IN_EN | REG3_ETH_REG3_19_RESVERD |
85                               REG3_CFG_PHY_ADDR | REG3_CFG_MODE |
86                               REG3_CFG_EN_HIGH | REG3_ETH_REG3_2_RESERVED,
87                           PER_ETH_REG3);
88 
89     // Enable clocks and power domain for dwmac
90     hhi_mmio_->SetBits32(1 << 3, HHI_GCLK_MPEG1);
91     hhi_mmio_->ClearBits32((1 << 3) | (1 << 2), HHI_MEM_PD_REG0);
92 
93     // WOL reset enable to MCU
94     uint8_t write_buf[2] = {MCU_I2C_REG_BOOT_EN_WOL, MCU_I2C_REG_BOOT_EN_WOL_RESET_ENABLE};
95     zx_status_t status = i2c_->WriteSync(write_buf, sizeof(write_buf));
96     if (status) {
97         zxlogf(ERROR, "aml-ethernet: WOL reset enable to MCU failed: %d\n", status);
98         return status;
99     }
100 
101     // Populate board specific information
102     eth_dev_metadata_t mac_info;
103     size_t actual;
104     status = device_get_metadata(parent(), DEVICE_METADATA_PRIVATE, &mac_info,
105                                  sizeof(eth_dev_metadata_t), &actual);
106     if (status != ZX_OK || actual != sizeof(eth_dev_metadata_t)) {
107         zxlogf(ERROR, "aml-ethernet: Could not get MAC metadata %d\n", status);
108         return status;
109     }
110 
111     zx_device_prop_t props[] = {
112         {BIND_PLATFORM_DEV_VID, 0, mac_info.vid},
113         {BIND_PLATFORM_DEV_DID, 0, mac_info.did},
114     };
115 
116     device_add_args_t args = {};
117     args.version = DEVICE_ADD_ARGS_VERSION;
118     args.name = "aml-ethernet";
119     args.ctx = this;
120     args.ops = &ddk_device_proto_;
121     args.proto_id = ddk_proto_id_;
122     args.proto_ops = ddk_proto_ops_;
123     args.props = props;
124     args.prop_count = countof(props);
125 
126     return pdev_.DeviceAdd(0, &args, &zxdev_);
127 }
128 
DdkUnbind()129 void AmlEthernet::DdkUnbind() {
130     DdkRemove();
131 }
132 
DdkRelease()133 void AmlEthernet::DdkRelease() {
134     delete this;
135 }
136 
Create(zx_device_t * parent)137 zx_status_t AmlEthernet::Create(zx_device_t* parent) {
138     fbl::AllocChecker ac;
139     auto eth_device = fbl::make_unique_checked<AmlEthernet>(&ac, parent);
140     if (!ac.check()) {
141         return ZX_ERR_NO_MEMORY;
142     }
143 
144     zx_status_t status = eth_device->InitPdev();
145     if (status != ZX_OK) {
146         return status;
147     }
148 
149     status = eth_device->Bind();
150     if (status != ZX_OK) {
151         zxlogf(ERROR, "aml-ethernet driver failed to get added: %d\n", status);
152         return status;
153     } else {
154         zxlogf(INFO, "aml-ethernet driver added\n");
155     }
156 
157     // eth_device intentionally leaked as it is now held by DevMgr
158     __UNUSED auto ptr = eth_device.release();
159 
160     return ZX_OK;
161 }
162 
163 } // namespace eth
164 
aml_eth_bind(void * ctx,zx_device_t * parent)165 extern "C" zx_status_t aml_eth_bind(void* ctx, zx_device_t* parent) {
166     return eth::AmlEthernet::Create(parent);
167 }
168