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 = ð_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(ð_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(ð_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(ð_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(ð_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(ð_device->pdev_, 0, ð_mac_dev_args, ð_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