blob: 8f2402cb59357ac5433fe088307f01ed2b38cec6 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "aml-ethernet.h"
#include "aml-regs.h"
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/driver.h>
#include <ddk/metadata.h>
#include <ddk/platform-defs.h>
#include <ddk/protocol/i2c-lib.h>
#include <ddk/protocol/composite.h>
#include <ddk/protocol/ethernet.h>
#include <ddk/protocol/platform/device.h>
#include <ddk/protocol/platform-device-lib.h>
#include <fbl/algorithm.h>
#include <fbl/auto_call.h>
#include <fbl/auto_lock.h>
#include <fbl/unique_ptr.h>
#include <hw/reg.h>
#include <soc/aml-s912/s912-hw.h>
#include <stdio.h>
#include <string.h>
#include <zircon/compiler.h>
namespace eth {
#define MCU_I2C_REG_BOOT_EN_WOL 0x21
#define MCU_I2C_REG_BOOT_EN_WOL_RESET_ENABLE 0x03
zx_status_t AmlEthernet::EthBoardResetPhy() {
gpios_[PHY_RESET].Write(0);
zx_nanosleep(zx_deadline_after(ZX_MSEC(100)));
gpios_[PHY_RESET].Write(1);
zx_nanosleep(zx_deadline_after(ZX_MSEC(100)));
return ZX_OK;
}
zx_status_t AmlEthernet::InitPdev() {
composite_protocol_t composite;
auto status = device_get_protocol(parent(), ZX_PROTOCOL_COMPOSITE, &composite);
if (status != ZX_OK) {
zxlogf(ERROR, "Could not get composite protocol\n");
return status;
}
zx_device_t* components[COMPONENT_COUNT];
size_t actual;
composite_get_components(&composite, components, fbl::count_of(components), &actual);
if (actual != fbl::count_of(components)) {
zxlogf(ERROR, "could not get components\n");
return ZX_ERR_NOT_SUPPORTED;
}
pdev_protocol_t pdev;
status = device_get_protocol(components[COMPONENT_PDEV], ZX_PROTOCOL_PDEV, &pdev);
if (status != ZX_OK) {
zxlogf(ERROR, "Could not get PDEV protocol\n");
return status;
}
pdev_ = &pdev;
i2c_protocol_t i2c;
status = device_get_protocol(components[COMPONENT_I2C], ZX_PROTOCOL_I2C, &i2c);
if (status != ZX_OK) {
zxlogf(ERROR, "Could not get I2C protocol\n");
return status;
}
i2c_ = &i2c;
gpio_protocol_t gpio;
status = device_get_protocol(components[COMPONENT_RESET_GPIO], ZX_PROTOCOL_GPIO, &gpio);
if (status != ZX_OK) {
zxlogf(ERROR, "Could not get GPIO protocol\n");
return status;
}
gpios_[PHY_RESET] = &gpio;
status = device_get_protocol(components[COMPONENT_INTR_GPIO], ZX_PROTOCOL_GPIO, &gpio);
if (status != ZX_OK) {
zxlogf(ERROR, "Could not get GPIO protocol\n");
return status;
}
gpios_[PHY_INTR] = &gpio;
// Map amlogic peripheral control registers.
status = pdev_.MapMmio(MMIO_PERIPH, &periph_mmio_);
if (status != ZX_OK) {
zxlogf(ERROR, "aml-dwmac: could not map periph mmio: %d\n", status);
return status;
}
// Map HHI regs (clocks and power domains).
status = pdev_.MapMmio(MMIO_HHI, &hhi_mmio_);
if (status != ZX_OK) {
zxlogf(ERROR, "aml-dwmac: could not map hiu mmio: %d\n", status);
return status;
}
return status;
}
zx_status_t AmlEthernet::Bind() {
// Set reset line to output
gpios_[PHY_RESET].ConfigOut(0);
// Initialize AMLogic peripheral registers associated with dwmac.
//Sorry about the magic...rtfm
periph_mmio_->Write32(0x1621, PER_ETH_REG0);
periph_mmio_->Write32(0x20000, PER_ETH_REG1);
periph_mmio_->Write32(REG2_ETH_REG2_REVERSED | REG2_INTERNAL_PHY_ID, PER_ETH_REG2);
periph_mmio_->Write32(REG3_CLK_IN_EN | REG3_ETH_REG3_19_RESVERD |
REG3_CFG_PHY_ADDR | REG3_CFG_MODE |
REG3_CFG_EN_HIGH | REG3_ETH_REG3_2_RESERVED,
PER_ETH_REG3);
// Enable clocks and power domain for dwmac
hhi_mmio_->SetBits32(1 << 3, HHI_GCLK_MPEG1);
hhi_mmio_->ClearBits32((1 << 3) | (1 << 2), HHI_MEM_PD_REG0);
// WOL reset enable to MCU
uint8_t write_buf[2] = {MCU_I2C_REG_BOOT_EN_WOL, MCU_I2C_REG_BOOT_EN_WOL_RESET_ENABLE};
zx_status_t status = i2c_.WriteSync(write_buf, sizeof(write_buf));
if (status) {
zxlogf(ERROR, "aml-ethernet: WOL reset enable to MCU failed: %d\n", status);
return status;
}
// Populate board specific information
eth_dev_metadata_t mac_info;
size_t actual;
status = device_get_metadata(parent(), DEVICE_METADATA_ETH_MAC_DEVICE, &mac_info,
sizeof(eth_dev_metadata_t), &actual);
if (status != ZX_OK || actual != sizeof(eth_dev_metadata_t)) {
zxlogf(ERROR, "aml-ethernet: Could not get MAC metadata %d\n", status);
return status;
}
zx_device_prop_t props[] = {
{BIND_PLATFORM_DEV_VID, 0, mac_info.vid},
{BIND_PLATFORM_DEV_DID, 0, mac_info.did},
};
return DdkAdd("aml-ethernet", 0, props, fbl::count_of(props));
}
void AmlEthernet::DdkUnbind() {
DdkRemove();
}
void AmlEthernet::DdkRelease() {
delete this;
}
zx_status_t AmlEthernet::Create(void* ctx, zx_device_t* parent) {
fbl::AllocChecker ac;
auto eth_device = fbl::make_unique_checked<AmlEthernet>(&ac, parent);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
zx_status_t status = eth_device->InitPdev();
if (status != ZX_OK) {
return status;
}
status = eth_device->Bind();
if (status != ZX_OK) {
zxlogf(ERROR, "aml-ethernet driver failed to get added: %d\n", status);
return status;
} else {
zxlogf(INFO, "aml-ethernet driver added\n");
}
// eth_device intentionally leaked as it is now held by DevMgr
__UNUSED auto ptr = eth_device.release();
return ZX_OK;
}
static zx_driver_ops_t driver_ops = [](){
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = AmlEthernet::Create;
return ops;
}();
} // namespace eth
// clang-format off
ZIRCON_DRIVER_BEGIN(aml_eth, eth::driver_ops, "aml-ethernet", "0.1", 4)
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_COMPOSITE),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_AMLOGIC),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_PID, PDEV_PID_AMLOGIC_S912),
BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_AMLOGIC_ETH),
ZIRCON_DRIVER_END(aml_eth)