// 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 "imx227.h" #include "imx227-seq.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace camera { namespace { constexpr uint16_t kSensorId = 0x0227; constexpr uint32_t kAGainPrecision = 12; constexpr uint32_t kDGainPrecision = 8; constexpr int32_t kLog2GainShift = 18; constexpr int32_t kSensorExpNumber = 1; constexpr uint32_t kMasterClock = 288000000; } // namespace zx_status_t Imx227Device::InitPdev(zx_device_t* parent) { if (!pdev_.is_valid()) { zxlogf(ERROR, "%s: ZX_PROTOCOL_PDEV not available\n", __FUNCTION__); return ZX_ERR_NO_RESOURCES; } for (uint32_t i = 0; i < countof(gpios_); i++) { std::optional gpio; gpio = pdev_.GetGpio(i); if (!gpio) { return ZX_ERR_NO_RESOURCES; } gpios_[i] = *gpio; // Set the GPIO to output and set initial value to 0. gpios_[i].ConfigOut(0); } // I2c for communicating with the sensor. if (i2c_.is_valid()) { return ZX_ERR_NO_RESOURCES; } // Clk for gating clocks for sensor. if (clk_.is_valid()) { return ZX_ERR_NO_RESOURCES; } // Mipi for init and de-init. if (!mipi_.is_valid()) { return ZX_ERR_NO_RESOURCES; } return ZX_OK; } uint8_t Imx227Device::ReadReg(uint16_t addr) { // Convert the address to Big Endian format. // The camera sensor expects in this format. uint16_t buf = htobe16(addr); uint8_t val = 0; zx_status_t status = i2c_.WriteReadSync(reinterpret_cast(&buf), sizeof(buf), &val, sizeof(val)); if (status != ZX_OK) { zxlogf(ERROR, "Imx227Device: could not read reg addr: 0x%08x status: %d\n", addr, status); return -1; } return val; } void Imx227Device::WriteReg(uint16_t addr, uint8_t val) { // Convert the address to Big Endian format. // The camera sensor expects in this format. // First two bytes are the address, third one is the value to be written. uint8_t buf[3]; buf[1] = static_cast(addr & 0xFF); buf[0] = static_cast((addr >> 8) & 0xFF); buf[2] = val; zx_status_t status = i2c_.WriteSync(buf, 3); if (status != ZX_OK) { zxlogf(ERROR, "Imx227Device: could not write reg addr/val: 0x%08x/0x%08x status: %d\n", addr, val, status); } } bool Imx227Device::ValidateSensorID() { uint16_t sensor_id = static_cast((ReadReg(0x0016) << 8) | ReadReg(0x0017)); if (sensor_id != kSensorId) { zxlogf(ERROR, "Imx227Device: Invalid sensor ID\n"); return false; } return true; } zx_status_t Imx227Device::InitSensor(uint8_t idx) { if (idx >= countof(kSEQUENCE_TABLE)) { return ZX_ERR_INVALID_ARGS; } const init_seq_fmt_t* sequence = kSEQUENCE_TABLE[idx]; bool init_command = true; while (init_command) { uint16_t address = sequence->address; uint8_t value = sequence->value; switch (address) { case 0x0000: { if (sequence->value == 0 && sequence->len == 0) { init_command = false; } else { WriteReg(address, value); } break; } default: WriteReg(address, value); break; } sequence++; } return ZX_OK; } zx_status_t Imx227Device::Init() { // Power up sequence. Reference: Page 51- IMX227-0AQH5-C datasheet. gpios_[VANA_ENABLE].ConfigOut(1); zx_nanosleep(zx_deadline_after(ZX_MSEC(50))); gpios_[VDIG_ENABLE].ConfigOut(1); zx_nanosleep(zx_deadline_after(ZX_MSEC(50))); // Enable 24M clock for sensor. clk_.Enable(0); zx_nanosleep(zx_deadline_after(ZX_MSEC(10))); gpios_[CAM_SENSOR_RST].ConfigOut(0); zx_nanosleep(zx_deadline_after(ZX_MSEC(50))); // Get Sensor ID to validate initialization sequence. if (!ValidateSensorID()) { return ZX_ERR_INTERNAL; } // Initialize Sensor Context. ctx_.seq_width = 1; ctx_.streaming_flag = 0; ctx_.again_old = 0; ctx_.change_flag = 0; ctx_.again_limit = 8 << kAGainPrecision; ctx_.dgain_limit = 15 << kDGainPrecision; // Initialize Sensor Parameters. ctx_.param.again_accuracy = 1 << kLog2GainShift; ctx_.param.sensor_exp_number = kSensorExpNumber; ctx_.param.again_log2_max = 3 << kLog2GainShift; ctx_.param.dgain_log2_max = 3 << kLog2GainShift; ctx_.param.integration_time_apply_delay = 2; ctx_.param.isp_exposure_channel_delay = 0; return ZX_OK; } void Imx227Device::DeInit() { mipi_.DeInit(); } zx_status_t Imx227Device::GetInfo(fuchsia_hardware_camera_SensorInfo* out_info) { return ZX_ERR_NOT_SUPPORTED; } zx_status_t Imx227Device::SetMode(uint8_t mode) { // Get Sensor ID to see if sensor is initialized. if (!ValidateSensorID()) { return ZX_ERR_INTERNAL; } if (mode >= countof(supported_modes)) { return ZX_ERR_INVALID_ARGS; } switch (supported_modes[mode].wdr_mode) { case kWDR_MODE_LINEAR: { InitSensor(supported_modes[mode].idx); ctx_.again_delay = 0; ctx_.dgain_delay = 0; ctx_.param.integration_time_apply_delay = 2; ctx_.param.isp_exposure_channel_delay = 0; ctx_.hdr_flag = 0; break; } // TODO(braval) : Support other modes. default: return ZX_ERR_NOT_SUPPORTED; } ctx_.param.active.width = supported_modes[mode].resolution.width; ctx_.param.active.height = supported_modes[mode].resolution.height; ctx_.HMAX = static_cast(ReadReg(0x342) << 8 | ReadReg(0x343)); ctx_.VMAX = static_cast(ReadReg(0x340) << 8 | ReadReg(0x341)); ctx_.int_max = 0x0A8C; // Max allowed for 30fps = 2782 (dec), 0x0A8E (hex) ctx_.int_time_min = 1; ctx_.int_time_limit = ctx_.int_max; ctx_.param.total.height = ctx_.VMAX; ctx_.param.total.width = ctx_.HMAX; ctx_.param.pixels_per_line = ctx_.param.total.width; uint32_t master_clock = kMasterClock; ctx_.param.lines_per_second = master_clock / ctx_.HMAX; ctx_.param.integration_time_min = ctx_.int_time_min; ctx_.param.integration_time_limit = ctx_.int_time_limit; ctx_.param.integration_time_max = ctx_.int_time_limit; ctx_.param.integration_time_long_max = ctx_.int_time_limit; ctx_.param.mode = mode; ctx_.param.bayer = supported_modes[mode].bayer; ctx_.wdr_mode = supported_modes[mode].wdr_mode; mipi_info_t mipi_info; mipi_adap_info_t adap_info; mipi_info.lanes = supported_modes[mode].lanes; mipi_info.ui_value = 1000 / supported_modes[mode].mbps; if ((1000 % supported_modes[mode].mbps) != 0) { mipi_info.ui_value += 1; } switch (supported_modes[mode].bits) { case 10: adap_info.format = IMAGE_FORMAT_AM_RAW10; break; case 12: adap_info.format = IMAGE_FORMAT_AM_RAW12; break; default: adap_info.format = IMAGE_FORMAT_AM_RAW10; break; } adap_info.resolution.width = supported_modes[mode].resolution.width; adap_info.resolution.height = supported_modes[mode].resolution.height; adap_info.path = MIPI_PATH_PATH0; adap_info.mode = MIPI_MODES_DDR_MODE; return mipi_.Init(&mipi_info, &adap_info); } void Imx227Device::StartStreaming() { ctx_.streaming_flag = 1; WriteReg(0x0100, 0x01); } void Imx227Device::StopStreaming() { ctx_.streaming_flag = 0; WriteReg(0x0100, 0x00); } int32_t Imx227Device::SetAnalogGain(int32_t gain) { return ZX_ERR_NOT_SUPPORTED; } int32_t Imx227Device::SetDigitalGain(int32_t gain) { return ZX_ERR_NOT_SUPPORTED; } void Imx227Device::SetIntegrationTime(int32_t int_time, int32_t int_time_M, int32_t int_time_L) { } void Imx227Device::Update() { } zx_status_t Imx227Device::DdkIoctl(uint32_t op, const void* in_buf, size_t in_len, void* out_buf, size_t out_len, size_t* out_actual) { switch (op) { case CAMERA_IOCTL_GET_SUPPORTED_MODES: { if (out_len < sizeof(fuchsia_hardware_camera_SensorMode) * MAX_SUPPORTED_MODES) { return ZX_ERR_BUFFER_TOO_SMALL; } memcpy(out_buf, &supported_modes, sizeof(supported_modes)); *out_actual = sizeof(supported_modes); return ZX_OK; } default: return ZX_ERR_NOT_SUPPORTED; } } static zx_status_t Init(void* ctx, fidl_txn_t* txn) { auto& self = *static_cast(ctx); zx_status_t status = self.Init(); return fuchsia_hardware_camera_CameraSensorInit_reply(txn, status); } static zx_status_t DeInit(void* ctx) { auto& self = *static_cast(ctx); self.DeInit(); return ZX_OK; } static zx_status_t SetMode(void* ctx, uint8_t mode, fidl_txn_t* txn) { auto& self = *static_cast(ctx); zx_status_t status = self.SetMode(mode); return fuchsia_hardware_camera_CameraSensorSetMode_reply(txn, status); } static zx_status_t StartStreaming(void* ctx) { auto& self = *static_cast(ctx); self.StartStreaming(); return ZX_OK; } static zx_status_t StopStreaming(void* ctx) { auto& self = *static_cast(ctx); self.StopStreaming(); return ZX_OK; } static zx_status_t SetAnalogGain(void* ctx, int32_t gain, fidl_txn_t* txn) { auto& self = *static_cast(ctx); int32_t actual_gain = self.SetAnalogGain(gain); return fuchsia_hardware_camera_CameraSensorSetAnalogGain_reply(txn, actual_gain); } static zx_status_t SetDigitalGain(void* ctx, int32_t gain, fidl_txn_t* txn) { auto& self = *static_cast(ctx); int32_t actual_gain = self.SetDigitalGain(gain); return fuchsia_hardware_camera_CameraSensorSetDigitalGain_reply(txn, actual_gain); } static zx_status_t SetIntegrationTime(void* ctx, int32_t int_time, int32_t int_time_M, int32_t int_time_L) { auto& self = *static_cast(ctx); self.SetIntegrationTime(int_time, int_time_M, int_time_L); return ZX_OK; } static zx_status_t Update(void* ctx) { auto& self = *static_cast(ctx); self.Update(); return ZX_OK; } fuchsia_hardware_camera_CameraSensor_ops_t fidl_ops = { .Init = Init, .DeInit = DeInit, .SetMode = SetMode, .StartStreaming = StartStreaming, .StopStreaming = StopStreaming, .SetAnalogGain = SetAnalogGain, .SetDigitalGain = SetDigitalGain, .SetIntegrationTime = SetIntegrationTime, .Update = Update, }; zx_status_t Imx227Device::DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn) { return fuchsia_hardware_camera_CameraSensor_dispatch(this, txn, msg, &fidl_ops); } zx_status_t Imx227Device::Create(zx_device_t* parent) { fbl::AllocChecker ac; auto sensor_device = fbl::make_unique_checked(&ac, parent); if (!ac.check()) { return ZX_ERR_NO_MEMORY; } zx_status_t status = sensor_device->InitPdev(parent); if (status != ZX_OK) { return status; } status = sensor_device->DdkAdd("imx227"); // sensor_device intentionally leaked as it is now held by DevMgr. __UNUSED auto ptr = sensor_device.release(); return status; } void Imx227Device::ShutDown() { } void Imx227Device::DdkUnbind() { DdkRemove(); } void Imx227Device::DdkRelease() { ShutDown(); delete this; } } // namespace camera extern "C" zx_status_t imx227_bind(void* ctx, zx_device_t* device) { return camera::Imx227Device::Create(device); }