| // 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 <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <ddk/binding.h> |
| #include <ddk/debug.h> |
| #include <ddk/device.h> |
| #include <ddk/driver.h> |
| #include <ddk/protocol/iommu.h> |
| #include <ddk/protocol/platform-bus.h> |
| #include <ddk/protocol/platform-defs.h> |
| #include <ddk/protocol/platform-device.h> |
| #include <hw/reg.h> |
| #include <soc/aml-common/aml-gpu.h> |
| #include <zircon/process.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/device/gpu.h> |
| #include "s912-gpu.h" |
| #include "s905d2-gpu.h" |
| |
| static int32_t current_clk_source; |
| |
| static void aml_gpu_set_clk_freq_source(aml_gpu_t* gpu, int32_t clk_source) { |
| |
| if (current_clk_source == clk_source) { |
| return; |
| } |
| |
| aml_gpu_block_t* gpu_block = gpu->gpu_block; |
| uint32_t current_clk_cntl = READ32_HIU_REG(gpu_block->hhi_clock_cntl_offset); |
| uint32_t enabled_mux = current_clk_cntl & (1 << FINAL_MUX_BIT_SHIFT); |
| uint32_t new_mux = enabled_mux == 0; |
| uint32_t mux_shift = new_mux ? 16 : 0; |
| |
| // clear existing values |
| current_clk_cntl &= ~(CLOCK_MUX_MASK << mux_shift); |
| // set the divisor, enable & source for the unused mux |
| current_clk_cntl |= CALCULATE_CLOCK_MUX(true, |
| gpu_block->gpu_clk_freq[clk_source], 1) << mux_shift; |
| |
| // Write the new values to the unused mux |
| WRITE32_HIU_REG(gpu_block->hhi_clock_cntl_offset, current_clk_cntl); |
| zx_nanosleep(zx_deadline_after(ZX_USEC(10))); |
| |
| // Toggle current mux selection |
| current_clk_cntl ^= (1 << FINAL_MUX_BIT_SHIFT); |
| |
| // Select the unused input mux |
| WRITE32_HIU_REG(gpu_block->hhi_clock_cntl_offset, current_clk_cntl); |
| |
| current_clk_source = clk_source; |
| } |
| |
| static void aml_gpu_init(aml_gpu_t* gpu) { |
| uint32_t temp; |
| aml_gpu_block_t* gpu_block = gpu->gpu_block; |
| |
| temp = READ32_PRESET_REG(gpu_block->reset0_mask_offset); |
| temp &= ~(1 << 20); |
| WRITE32_PRESET_REG(gpu_block->reset0_mask_offset, temp); |
| |
| temp = READ32_PRESET_REG(gpu_block->reset0_level_offset); |
| temp &= ~(1 << 20); |
| WRITE32_PRESET_REG(gpu_block->reset0_level_offset, temp); |
| |
| temp = READ32_PRESET_REG(gpu_block->reset2_mask_offset); |
| temp &= ~(1 << 14); |
| WRITE32_PRESET_REG(gpu_block->reset2_mask_offset, temp); |
| |
| temp = READ32_PRESET_REG(gpu_block->reset2_level_offset); |
| temp &= ~(1 << 14); |
| WRITE32_PRESET_REG(gpu_block->reset2_level_offset, temp); |
| |
| // Currently the index 2 corresponds to the default |
| // value of GPU clock freq which is 500Mhz. |
| // In future, the GPU driver in garnet |
| // can make an IOCTL to set the default freq |
| aml_gpu_set_clk_freq_source(gpu, 2); |
| |
| temp = READ32_PRESET_REG(gpu_block->reset0_level_offset); |
| temp |= 1 << 20; |
| WRITE32_PRESET_REG(gpu_block->reset0_level_offset, temp); |
| |
| temp = READ32_PRESET_REG(gpu_block->reset2_level_offset); |
| temp |= 1 << 14; |
| WRITE32_PRESET_REG(gpu_block->reset2_level_offset, temp); |
| |
| WRITE32_GPU_REG(PWR_KEY, 0x2968A819); |
| WRITE32_GPU_REG(PWR_OVERRIDE1, 0xfff | (0x20 << 16)); |
| } |
| |
| static void aml_gpu_release(void* ctx) { |
| aml_gpu_t* gpu = ctx; |
| io_buffer_release(&gpu->hiu_buffer); |
| io_buffer_release(&gpu->preset_buffer); |
| io_buffer_release(&gpu->gpu_buffer); |
| free(gpu); |
| } |
| |
| static zx_status_t aml_gpu_get_protocol(void* ctx, uint32_t proto_id, void* out_proto) { |
| |
| aml_gpu_t* gpu = ctx; |
| platform_device_protocol_t* gpu_proto = out_proto; |
| |
| // Forward the underlying ops. |
| gpu_proto->ops = gpu->pdev.ops; |
| gpu_proto->ctx = gpu->pdev.ctx; |
| return ZX_OK; |
| } |
| |
| static zx_status_t aml_gpu_ioctl(void* ctx, uint32_t op, |
| const void* in_buf, size_t in_len, |
| void* out_buf, size_t out_len, |
| size_t* out_actual) { |
| aml_gpu_t* gpu = ctx; |
| switch(op) { |
| case IOCTL_GPU_SET_CLK_FREQ_SOURCE: { |
| if (in_len != sizeof(int32_t)) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| int32_t *clk_source = (int32_t*)in_buf; |
| |
| if (*clk_source >= MAX_GPU_CLK_FREQ) { |
| GPU_ERROR("Invalid clock freq source index\n"); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| aml_gpu_set_clk_freq_source(gpu, *clk_source); |
| return ZX_OK; |
| } |
| default: |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| } |
| |
| static zx_protocol_device_t aml_gpu_protocol = { |
| .version = DEVICE_OPS_VERSION, |
| .release = aml_gpu_release, |
| .get_protocol = aml_gpu_get_protocol, |
| .ioctl = aml_gpu_ioctl, |
| }; |
| |
| static zx_status_t aml_gpu_bind(void* ctx, zx_device_t* parent) { |
| zx_status_t status; |
| aml_gpu_t* gpu = calloc(1, sizeof(aml_gpu_t)); |
| if (!gpu) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| if ((status = device_get_protocol(parent, ZX_PROTOCOL_PLATFORM_DEV, &gpu->pdev)) != ZX_OK) { |
| GPU_ERROR("ZX_PROTOCOL_PLATFORM_DEV not available\n"); |
| goto fail; |
| } |
| |
| status = pdev_map_mmio_buffer(&gpu->pdev, MMIO_GPU, ZX_CACHE_POLICY_UNCACHED_DEVICE, |
| &gpu->gpu_buffer); |
| if (status != ZX_OK) { |
| GPU_ERROR("pdev_map_mmio_buffer failed\n"); |
| goto fail; |
| } |
| |
| status = pdev_map_mmio_buffer(&gpu->pdev, MMIO_HIU, ZX_CACHE_POLICY_UNCACHED_DEVICE, |
| &gpu->hiu_buffer); |
| if (status != ZX_OK) { |
| GPU_ERROR("pdev_map_mmio_buffer failed\n"); |
| goto fail; |
| } |
| |
| status = pdev_map_mmio_buffer(&gpu->pdev, MMIO_PRESET, ZX_CACHE_POLICY_UNCACHED_DEVICE, |
| &gpu->preset_buffer); |
| if (status != ZX_OK) { |
| GPU_ERROR("pdev_map_mmio_buffer failed\n"); |
| goto fail; |
| } |
| |
| pdev_device_info_t info; |
| status = pdev_get_device_info(&gpu->pdev, &info); |
| if (status != ZX_OK) { |
| GPU_ERROR("pdev_get_device_info failed\n"); |
| goto fail; |
| } |
| |
| switch (info.pid) { |
| case PDEV_PID_AMLOGIC_S912: |
| gpu->gpu_block = &s912_gpu_blocks; |
| break; |
| case PDEV_PID_AMLOGIC_S905D2: |
| gpu->gpu_block = &s905d2_gpu_blocks; |
| break; |
| default: |
| GPU_ERROR("unsupported SOC PID %u\n", info.pid); |
| goto fail; |
| } |
| |
| aml_gpu_init(gpu); |
| |
| zx_device_prop_t props[] = { |
| {BIND_PROTOCOL, 0, ZX_PROTOCOL_PLATFORM_DEV}, |
| {BIND_PLATFORM_DEV_VID, 0, PDEV_VID_GENERIC}, |
| {BIND_PLATFORM_DEV_PID, 0, PDEV_PID_GENERIC}, |
| {BIND_PLATFORM_DEV_DID, 0, PDEV_DID_ARM_MALI}, |
| }; |
| |
| device_add_args_t args = { |
| .version = DEVICE_ADD_ARGS_VERSION, |
| .name = "aml-gpu", |
| .ctx = gpu, |
| .ops = &aml_gpu_protocol, |
| .props = props, |
| .prop_count = countof(props), |
| .proto_id = ZX_PROTOCOL_GPU_THERMAL, |
| }; |
| |
| status = device_add(parent, &args, &gpu->zxdev); |
| if (status != ZX_OK) { |
| goto fail; |
| } |
| |
| return ZX_OK; |
| |
| fail: |
| aml_gpu_release(gpu); |
| return status; |
| } |
| |
| static zx_driver_ops_t aml_gpu_driver_ops = { |
| .version = DRIVER_OPS_VERSION, |
| .bind = aml_gpu_bind, |
| }; |
| |
| ZIRCON_DRIVER_BEGIN(aml_gpu, aml_gpu_driver_ops, "zircon", "0.1", 5) |
| BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PLATFORM_DEV), |
| BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_AMLOGIC), |
| BI_ABORT_IF(NE, BIND_PLATFORM_DEV_DID, PDEV_DID_ARM_MALI_INIT), |
| // we support multiple SOC variants |
| BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_PID, PDEV_PID_AMLOGIC_S912), |
| BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_PID, PDEV_PID_AMLOGIC_S905D2), |
| ZIRCON_DRIVER_END(aml_gpu) |