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