| // 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 <endian.h> |
| #include <lib/device-protocol/i2c.h> |
| #include <lib/driver-unit-test/utils.h> |
| #include <lib/fit/result.h> |
| #include <lib/trace/event.h> |
| #include <threads.h> |
| #include <zircon/types.h> |
| |
| #include <ddk/debug.h> |
| #include <ddk/metadata.h> |
| #include <ddk/metadata/camera.h> |
| #include <ddk/protocol/camera/sensor.h> |
| #include <fbl/auto_call.h> |
| #include <fbl/auto_lock.h> |
| #include <hw/reg.h> |
| |
| #include "src/camera/drivers/sensors/imx227/bind.h" |
| #include "src/camera/drivers/sensors/imx227/constants.h" |
| #include "src/camera/drivers/sensors/imx227/imx227_modes.h" |
| #include "src/camera/drivers/sensors/imx227/imx227_seq.h" |
| #include "src/camera/drivers/sensors/imx227/mipi_ccs_regs.h" |
| |
| namespace camera { |
| |
| // Gets the register value from the sequence table. |
| // |id| : Index of the sequence table. |
| // |address| : Address of the register. |
| fit::result<uint8_t, zx_status_t> Imx227Device::GetRegisterValueFromSequence(uint8_t index, |
| uint16_t address) { |
| TRACE_DURATION("camera", "Imx227Device::GetRegisterValueFromSequence"); |
| if (index >= kSEQUENCE_TABLE.size()) { |
| return fit::error(ZX_ERR_INVALID_ARGS); |
| } |
| const InitSeqFmt* sequence = kSEQUENCE_TABLE[index]; |
| while (true) { |
| uint16_t register_address = sequence->address; |
| uint16_t register_value = sequence->value; |
| uint16_t register_len = sequence->len; |
| if (register_address == kEndOfSequence && register_value == 0 && register_len == 0) { |
| break; |
| } |
| if (address == register_address) { |
| return fit::ok(register_value); |
| } |
| sequence++; |
| } |
| return fit::error(ZX_ERR_NOT_FOUND); |
| } |
| |
| zx_status_t Imx227Device::InitPdev() { |
| std::lock_guard guard(lock_); |
| |
| // I2c for communicating with the sensor. |
| if (!i2c_.is_valid()) { |
| zxlogf(ERROR, "%s; I2C not available", __func__); |
| return ZX_ERR_NO_RESOURCES; |
| } |
| |
| // Clk for gating clocks for sensor. |
| if (!clk24_.is_valid()) { |
| zxlogf(ERROR, "%s; clk24_ not available", __func__); |
| return ZX_ERR_NO_RESOURCES; |
| } |
| |
| // Mipi for init and de-init. |
| if (!mipi_.is_valid()) { |
| zxlogf(ERROR, "%s; mipi_ not available", __func__); |
| return ZX_ERR_NO_RESOURCES; |
| } |
| |
| // GPIOs |
| if (!gpio_vana_enable_.is_valid()) { |
| zxlogf(ERROR, "%s; gpio_vana_enable_ not available", __func__); |
| return ZX_ERR_NO_RESOURCES; |
| } |
| |
| if (!gpio_vdig_enable_.is_valid()) { |
| zxlogf(ERROR, "%s; gpio_vdig_enable_ not available", __func__); |
| return ZX_ERR_NO_RESOURCES; |
| } |
| |
| if (!gpio_cam_rst_.is_valid()) { |
| zxlogf(ERROR, "%s; gpio_cam_rst_ not available", __func__); |
| return ZX_ERR_NO_RESOURCES; |
| } |
| |
| // Set the GPIO to output and set them to their initial values |
| // before the power up sequence. |
| gpio_cam_rst_.ConfigOut(1); |
| gpio_vana_enable_.ConfigOut(0); |
| gpio_vdig_enable_.ConfigOut(0); |
| return ZX_OK; |
| } |
| |
| fit::result<uint16_t, zx_status_t> Imx227Device::Read16(uint16_t addr) { |
| TRACE_DURATION("camera", "Imx227Device::Read16", "addr", addr); |
| auto result = Read8(addr); |
| if (result.is_error()) { |
| return result.take_error_result(); |
| } |
| auto upper_byte = result.value(); |
| result = Read8(addr + 1); |
| if (result.is_error()) { |
| return result.take_error_result(); |
| } |
| auto lower_byte = result.value(); |
| uint16_t reg_value = upper_byte << kByteShift | lower_byte; |
| return fit::ok(reg_value); |
| } |
| |
| fit::result<uint8_t, zx_status_t> Imx227Device::Read8(uint16_t addr) { |
| TRACE_DURATION("camera", "Imx227Device::Read8", "addr", addr); |
| // Convert the address to Big Endian format. |
| // The camera sensor expects in this format. |
| uint16_t buf = htobe16(addr); |
| uint8_t val = 0; |
| auto status = |
| i2c_.WriteReadSync(reinterpret_cast<uint8_t*>(&buf), sizeof(buf), &val, sizeof(val)); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Imx227Device: could not read reg addr: 0x%08x status: %d", addr, status); |
| return fit::error(status); |
| } |
| return fit::ok(val); |
| } |
| |
| zx_status_t Imx227Device::Write16(uint16_t addr, uint16_t val) { |
| TRACE_DURATION("camera", "Imx227Device::Write16", "addr", addr, "val", val); |
| // Convert the arguments to big endian to match the register spec. |
| // First two bytes are the address, third and fourth are the value to be written. |
| addr = htobe16(addr); |
| val = htobe16(val); |
| std::array<uint8_t, 4> buf; |
| buf[0] = static_cast<uint8_t>(addr & kByteMask); |
| buf[1] = static_cast<uint8_t>((addr >> kByteShift) & kByteMask); |
| buf[2] = static_cast<uint8_t>(val & kByteMask); |
| buf[3] = static_cast<uint8_t>((val >> kByteShift) & kByteMask); |
| auto status = i2c_.WriteSync(buf.data(), buf.size()); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, |
| "Imx227Device: could not write reg addr/val: 0x%08x/0x%08x status: " |
| "%d\n", |
| addr, val, status); |
| } |
| return status; |
| } |
| |
| zx_status_t Imx227Device::Write8(uint16_t addr, uint8_t val) { |
| TRACE_DURATION("camera", "Imx227Device::Write8", "addr", addr, "val", val); |
| // Convert the arguments to big endian to match the register spec. |
| // First two bytes are the address, third one is the value to be written. |
| addr = htobe16(addr); |
| std::array<uint8_t, 3> buf; |
| buf[0] = static_cast<uint8_t>(addr & kByteMask); |
| buf[1] = static_cast<uint8_t>((addr >> kByteShift) & kByteMask); |
| buf[2] = val; |
| |
| auto status = i2c_.WriteSync(buf.data(), buf.size()); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, |
| "Imx227Device: could not write reg addr/val: 0x%08x/0x%08x status: " |
| "%d\n", |
| addr, val, status); |
| } |
| return status; |
| } |
| |
| bool Imx227Device::ValidateSensorID() { |
| auto result = Read16(kSensorModelIdReg); |
| if (result.is_error()) { |
| return false; |
| } |
| return result.value() == kSensorId; |
| } |
| |
| zx_status_t Imx227Device::InitSensor(uint8_t idx) { |
| TRACE_DURATION("camera", "Imx227Device::InitSensor"); |
| if (idx >= kSEQUENCE_TABLE.size()) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| const InitSeqFmt* 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 { |
| Write8(address, value); |
| } |
| break; |
| } |
| default: |
| Write8(address, value); |
| break; |
| } |
| sequence++; |
| } |
| return ZX_OK; |
| } |
| |
| void Imx227Device::HwInit() { |
| TRACE_DURATION("camera", "Imx227Device::HwInit"); |
| // Power up sequence. Reference: Page 51- IMX227-0AQH5-C datasheet. |
| gpio_vana_enable_.Write(1); |
| zx_nanosleep(zx_deadline_after(ZX_MSEC(50))); |
| |
| gpio_vdig_enable_.Write(1); |
| zx_nanosleep(zx_deadline_after(ZX_MSEC(50))); |
| |
| // Enable 24M clock for sensor. |
| clk24_.Enable(); |
| zx_nanosleep(zx_deadline_after(ZX_MSEC(10))); |
| |
| gpio_cam_rst_.Write(0); |
| zx_nanosleep(zx_deadline_after(ZX_MSEC(50))); |
| } |
| |
| void Imx227Device::HwDeInit() { |
| TRACE_DURATION("camera", "Imx227Device::HwDeInit"); |
| gpio_cam_rst_.Write(1); |
| zx_nanosleep(zx_deadline_after(ZX_MSEC(50))); |
| |
| clk24_.Disable(); |
| zx_nanosleep(zx_deadline_after(ZX_MSEC(10))); |
| |
| gpio_vdig_enable_.Write(0); |
| zx_nanosleep(zx_deadline_after(ZX_MSEC(50))); |
| |
| gpio_vana_enable_.Write(0); |
| zx_nanosleep(zx_deadline_after(ZX_MSEC(50))); |
| } |
| |
| zx_status_t Imx227Device::InitMipiCsi(uint8_t mode) { |
| mipi_info_t mipi_info; |
| mipi_adap_info_t adap_info; |
| |
| mipi_info.lanes = available_modes[mode].lanes; |
| mipi_info.ui_value = 1000 / available_modes[mode].mbps; |
| if ((1000 % available_modes[mode].mbps) != 0) { |
| mipi_info.ui_value += 1; |
| } |
| |
| adap_info.format = MIPI_IMAGE_FORMAT_AM_RAW10; |
| adap_info.resolution.width = available_modes[mode].resolution_in.x; |
| adap_info.resolution.height = available_modes[mode].resolution_in.y; |
| adap_info.path = MIPI_PATH_PATH0; |
| adap_info.mode = MIPI_MODES_DIR_MODE; |
| auto status = mipi_.Init(&mipi_info, &adap_info); |
| return status; |
| } |
| |
| fit::result<int32_t, zx_status_t> Imx227Device::GetTemperature() { |
| std::lock_guard guard(lock_); |
| // Enable temperature control |
| zx_status_t status = Write8(kTempCtrlReg, 0x01); |
| if (status != ZX_OK) { |
| return fit::error(status); |
| } |
| auto result = Read8(kTempOutputReg); |
| if (result.is_error()) { |
| return result.take_error_result(); |
| } |
| auto retval = static_cast<int32_t>(result.value()); |
| return fit::ok(retval); |
| } |
| |
| fit::result<uint32_t, zx_status_t> Imx227Device::GetLinesPerSecond() { |
| auto result_hi = |
| GetRegisterValueFromSequence(available_modes[current_mode_].idx, kLineLengthPckReg); |
| auto result_lo = |
| GetRegisterValueFromSequence(available_modes[current_mode_].idx, kLineLengthPckReg + 1); |
| if (result_hi.is_error() || result_lo.is_error()) { |
| return fit::error(ZX_ERR_INTERNAL); |
| } |
| uint16_t line_length_pclk = (result_hi.value() << 8) | result_lo.value(); |
| uint32_t lines_per_second = kMasterClock / line_length_pclk; |
| return fit::ok(lines_per_second); |
| } |
| |
| float Imx227Device::AnalogRegValueToTotalGain(uint16_t reg_value) { |
| return (static_cast<float>(analog_gain_.m0_ * reg_value + analog_gain_.c0_)) / |
| (static_cast<float>(analog_gain_.m1_ * reg_value + analog_gain_.c1_)); |
| } |
| |
| uint16_t Imx227Device::AnalogTotalGainToRegValue(float gain) { |
| float value; |
| uint16_t register_value; |
| |
| // Compute the register value. |
| if (analog_gain_.m0_ == 0) { |
| value = ((analog_gain_.c0_ / gain) - analog_gain_.c1_) / analog_gain_.m1_; |
| } else { |
| value = (analog_gain_.c1_ * gain - analog_gain_.c0_) / analog_gain_.m0_; |
| } |
| |
| // Round the final result, which is quantized to the gain code step size. |
| value += 0.5f * analog_gain_.gain_code_step_size_; |
| |
| // Convert and clamp. |
| register_value = value; |
| |
| if (register_value < analog_gain_.gain_code_min_) { |
| register_value = analog_gain_.gain_code_min_; |
| } |
| |
| register_value = |
| (register_value - analog_gain_.gain_code_min_) / analog_gain_.gain_code_step_size_; |
| register_value = register_value * analog_gain_.gain_code_step_size_ + analog_gain_.gain_code_min_; |
| |
| if (register_value > analog_gain_.gain_code_max_) { |
| register_value = analog_gain_.gain_code_max_; |
| } |
| |
| return register_value; |
| } |
| |
| float Imx227Device::DigitalRegValueToTotalGain(uint16_t reg_value) { |
| return static_cast<float>(reg_value) / (1 << kDigitalGainShift); |
| } |
| |
| uint16_t Imx227Device::DigitalTotalGainToRegValue(float gain) { |
| float value; |
| uint16_t register_value; |
| |
| // Compute the register value. |
| value = gain * (1 << kDigitalGainShift); |
| |
| // Round the final result, which is quantized to the gain code step size. |
| value += 0.5f * digital_gain_.gain_step_size_; |
| |
| // Convert and clamp. |
| register_value = value; |
| |
| if (register_value < digital_gain_.gain_min_) { |
| register_value = digital_gain_.gain_min_; |
| } |
| |
| register_value = (register_value - digital_gain_.gain_min_) / digital_gain_.gain_step_size_; |
| register_value = register_value * digital_gain_.gain_step_size_ + digital_gain_.gain_min_; |
| |
| if (register_value > digital_gain_.gain_max_) { |
| register_value = digital_gain_.gain_max_; |
| } |
| |
| return register_value; |
| } |
| zx_status_t Imx227Device::ReadAnalogGainConstants() { |
| auto result_m0 = Read16(kAnalogGainM0Reg); |
| auto result_m1 = Read16(kAnalogGainM1Reg); |
| auto result_c0 = Read16(kAnalogGainC0Reg); |
| auto result_c1 = Read16(kAnalogGainC1Reg); |
| auto result_amin = Read16(kAnalogGainCodeMinReg); |
| auto result_amax = Read16(kAnalogGainCodeMaxReg); |
| auto result_astep = Read16(kAnalogGainCodeStepSizeReg); |
| |
| if (result_m0.is_error() || result_m1.is_error() || result_c0.is_error() || |
| result_c1.is_error() || result_amin.is_error() || result_amax.is_error() || |
| result_astep.is_error()) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| analog_gain_.m0_ = result_m0.value(); |
| analog_gain_.m1_ = result_m1.value(); |
| analog_gain_.c0_ = result_c0.value(); |
| analog_gain_.c1_ = result_c1.value(); |
| analog_gain_.gain_code_min_ = result_amin.value(); |
| analog_gain_.gain_code_max_ = result_amax.value(); |
| analog_gain_.gain_code_step_size_ = result_astep.value(); |
| |
| // Validate the m0,1 constraint |
| if (!(analog_gain_.m0_ == 0) ^ (analog_gain_.m1_ == 0)) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t Imx227Device::ReadDigitalGainConstants() { |
| auto result_dmin = Read16(kDigitalGainMinReg); |
| auto result_dmax = Read16(kDigitalGainMaxReg); |
| auto result_dstep = Read16(kDigitalGainStepSizeReg); |
| |
| if (result_dmin.is_error() || result_dmax.is_error() || result_dstep.is_error()) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| digital_gain_.gain_min_ = result_dmin.value(); |
| digital_gain_.gain_max_ = result_dmax.value(); |
| digital_gain_.gain_step_size_ = result_dstep.value(); |
| return ZX_OK; |
| } |
| |
| // TODO(jsasinowski): Determine if this can be called less frequently. |
| zx_status_t Imx227Device::ReadGainConstants() { |
| if (gain_constants_valid_) { |
| return ZX_OK; |
| } |
| |
| auto status = ReadAnalogGainConstants(); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| status = ReadDigitalGainConstants(); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| gain_constants_valid_ = true; |
| return ZX_OK; |
| } |
| |
| zx_status_t Imx227Device::SetGroupedParameterHold(bool enable) { |
| auto status = Write8(kGroupedParameterHoldReg, enable ? 1 : 0); |
| return status; |
| } |
| |
| zx_status_t Imx227Device::Create(zx_device_t* parent, std::unique_ptr<Imx227Device>* device_out) { |
| ddk::CompositeProtocolClient composite(parent); |
| if (!composite.is_valid()) { |
| zxlogf(ERROR, "%s could not get composite protocoln", __func__); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| auto sensor_device = std::make_unique<Imx227Device>(parent, composite); |
| |
| zx_status_t status = sensor_device->InitPdev(); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s InitPdev failed", __func__); |
| return status; |
| } |
| *device_out = std::move(sensor_device); |
| return status; |
| } |
| |
| void Imx227Device::ShutDown() {} |
| |
| void Imx227Device::DdkUnbind(ddk::UnbindTxn txn) { txn.Reply(); } |
| |
| void Imx227Device::DdkRelease() { |
| ShutDown(); |
| delete this; |
| } |
| |
| zx_status_t Imx227Device::CreateAndBind(void* /*ctx*/, zx_device_t* parent) { |
| std::unique_ptr<Imx227Device> device; |
| zx_status_t status = Imx227Device::Create(parent, &device); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "imx227: Could not setup imx227 sensor device: %d", status); |
| return status; |
| } |
| std::array<zx_device_prop_t, 1> props = {{ |
| {BIND_PLATFORM_PROTO, 0, ZX_PROTOCOL_CAMERA_SENSOR2}, |
| }}; |
| |
| status = device->DdkAdd( |
| ddk::DeviceAddArgs("imx227").set_flags(DEVICE_ADD_ALLOW_MULTI_COMPOSITE).set_props(props)); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "imx227: Could not add imx227 sensor device: %d", status); |
| return status; |
| } |
| zxlogf(INFO, "imx227 driver added"); |
| |
| // `device` intentionally leaked as it is now held by DevMgr. |
| __UNUSED auto* dev = device.release(); |
| return ZX_OK; |
| } |
| |
| bool Imx227Device::RunUnitTests(void* ctx, zx_device_t* parent, zx_handle_t channel) { |
| return driver_unit_test::RunZxTests("Imx227Tests", parent, channel); |
| } |
| |
| static constexpr zx_driver_ops_t driver_ops = []() { |
| zx_driver_ops_t ops = {}; |
| ops.version = DRIVER_OPS_VERSION; |
| ops.bind = Imx227Device::CreateAndBind; |
| ops.run_unit_tests = Imx227Device::RunUnitTests; |
| return ops; |
| }(); |
| |
| } // namespace camera |
| |
| // clang-format off |
| ZIRCON_DRIVER(imx227, camera::driver_ops, "imx227", "0.1"); |