blob: 7204b8916cf689a7466741d79c38b234449525b7 [file] [log] [blame]
/*
* Copyright (C) 2019 MediaTek Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "v4l2_lens_mgr"
#include <mtkcam/v4l2/property_strings.h>
#include <mtkcam/v4l2/V4L2LensMgr.h>
// MTKCAM
#include <mtkcam/utils/std/Log.h> // log
#include <property_lib.h>
// STL
#include <memory> // std::shared_ptr
#include <string> // std::string
#include <thread> // std::yield
#include <utility> // std::utility
#include <sys/ioctl.h> // ioctl
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h> // open
#include <linux/media.h>
#include <linux/v4l2-subdev.h>
#define V4L2LENSMGR_MAX_MDEV_NUM 5 // maximum media device number to enumerate
namespace v4l2 {
/* static instances */
std::once_flag V4L2LensMgr::m_onceFlgLogLvl;
int V4L2LensMgr::m_logLevel;
V4L2LensMgr::V4L2LensMgr(uint32_t sensorIdx)
: m_sensorIdx(sensorIdx), m_fd_sdev(-1) {
std::call_once(m_onceFlgLogLvl, []() {
m_logLevel = property_get_int32(PROP_V4L2_LENSMGR_LOGLEVEL, 2);
});
// create IHal3A
MAKE_Hal3A(
m_pHal3A, [](IHal3A* p) { p->destroyInstance(LOG_TAG); }, sensorIdx,
LOG_TAG);
if (openLensDriver() == false) {
CAM_LOGD("no lens driver to open");
}
m_lensCfgs.reserve(10); // avoid frequency reallocations
}
V4L2LensMgr::~V4L2LensMgr() {
if (m_fd_sdev != -1) {
::close(m_fd_sdev);
}
}
void V4L2LensMgr::validate() {
NS3Av3::IPC_LensConfig_T cfg = {0};
cfg.cmd = NS3Av3::IPC_LensConfig_T::ASK_TO_START; // ask to start
m_pHal3A->send3ACtrl(E3ACtrl_IPC_AF_ExchangeLensConfig,
reinterpret_cast<MINTPTR>(&cfg), 0);
}
void V4L2LensMgr::invalidate() {
NS3Av3::IPC_LensConfig_T cfg = {0};
cfg.cmd = NS3Av3::IPC_LensConfig_T::ASK_TO_STOP; // ask to stop
m_pHal3A->send3ACtrl(E3ACtrl_IPC_AF_ExchangeLensConfig,
reinterpret_cast<MINTPTR>(&cfg), 0);
}
int V4L2LensMgr::start() {
validate();
return V4L2DriverWorker::start();
}
int V4L2LensMgr::stop() {
// notify 3A framework to stop IPC
invalidate();
// stop job
return V4L2DriverWorker::stop();
}
void V4L2LensMgr::job() {
MBOOL result = 0;
IPC_LensConfig_T lensConfig = {0};
lensConfig.cmd = IPC_LensConfig_T::ASK_FOR_A_CMD;
/* deque a lens config from IHal3A */
result = m_pHal3A->send3ACtrl(E3ACtrl_IPC_AF_ExchangeLensConfig,
reinterpret_cast<MINTPTR>(&lensConfig), 0);
/* checks succeeded or not */
if (result != MTRUE || lensConfig.succeeded == 0) {
/* hint to reschedule */
std::this_thread::yield();
std::this_thread::sleep_for(std::chrono::milliseconds(1));
return;
}
switch (lensConfig.cmd) {
case IPC_LensConfig_T::CMD_FOCUS_ABSOULTE:
moveMCU(lensConfig.val.focus_pos);
break;
case IPC_LensConfig_T::CMD_IS_SUPPORT_LENS:
lensConfig.cmd = IPC_LensConfig_T::ACK_IS_SUPPORT_LENS;
lensConfig.val.is_support = isLensDriverOpened() ? 1 : 0;
lensConfig.succeeded = 1;
m_pHal3A->send3ACtrl(E3ACtrl_IPC_AF_ExchangeLensConfig,
reinterpret_cast<MINTPTR>(&lensConfig), 0);
break;
default:
CAM_LOGW("deque an IPC lens config but not support cmd(%d)",
lensConfig.cmd);
std::this_thread::yield();
std::this_thread::sleep_for(std::chrono::milliseconds(1));
return;
break;
}
}
void V4L2LensMgr::enqueConfig(const IPC_LensConfig_T& param) {
switch (param.cmd) {
case IPC_LensConfig_T::CMD_FOCUS_ABSOULTE: {
std::unique_lock<std::mutex> lk(m_lockLensCfg);
/* only keeps a CMD_FOCUS_ABSOULTE in container */
for (auto i = m_lensCfgs.begin(); i != m_lensCfgs.end();) {
if (i->cmd == IPC_LensConfig_T::CMD_FOCUS_ABSOULTE) {
i = m_lensCfgs.erase(i);
break; // basically, we do only have a CMD_FOCUS_ABSOULTE in
// container
} else {
++i;
}
}
m_lensCfgs.push_back(param);
lk.unlock();
CAM_LOGD("enqued lens config (FOCUS_ABSOLUTE");
m_condLensCfg.notify_all();
} break;
default:
break;
}
}
int V4L2LensMgr::dequeConfig(IPC_LensConfig_T* p_result) {
std::unique_lock<std::mutex> lk(m_lockLensCfg);
/* check still queuing or not */
if (m_bEnableQueuing.load(std::memory_order_relaxed) == false) {
return -EPERM;
}
/* if there's no configurations */
while (m_lensCfgs.size() <= 0) {
auto r = m_condLensCfg.wait_for(
lk, std::chrono::milliseconds(33)); // 33 ms per-frame
if (r == std::cv_status::timeout) {
return -ETIMEDOUT;
}
}
/* move the first element */
*p_result = std::move(m_lensCfgs.front());
m_lensCfgs.erase(m_lensCfgs.begin());
return 0;
}
bool V4L2LensMgr::openLensDriver() {
auto i2c_idx = 1 << (1 + m_sensorIdx); // sensor idx 0 --> i2c idx is 2
int r = getSubDevice(i2c_idx, &m_fd_sdev);
if (r != 0) {
CAM_LOGD("cannot find lens driver, errcode=%d", r);
return false;
}
return true;
}
int V4L2LensMgr::moveMCU(int64_t pos) {
if (isLensDriverOpened() == false) {
return -ENOENT;
}
struct v4l2_control control = {};
control.id = V4L2_CID_FOCUS_ABSOLUTE;
control.value = static_cast<int>(pos);
CAM_LOGD("lens subdev = %d, ctrl id = %d, value = %d", m_fd_sdev, control.id,
control.value);
int r = ioctl(m_fd_sdev, VIDIOC_S_CTRL, &control);
if (r != 0) {
CAM_LOGE("cannot set V4L2_CID_FOCUS_ABSOLUTE, err = %d", r);
} else {
CAM_LOGD("set focus absolutely to %d", (int)pos);
}
return r;
}
int V4L2LensMgr::getSubDevice(size_t i2c_idx, int* p_r_sdev_fd) {
char dev_name[64];
char subdev_name[64];
int num_media_devices = 0;
int dev_fd = -1, sd_fd = -1, r = 0;
/* traverse all media devices */
while (true) {
struct media_device_info mdev_info = {};
/* try to open media device */
::snprintf(dev_name, sizeof(dev_name), "/dev/media%d", num_media_devices++);
dev_fd = ::open(dev_name, O_RDWR | O_NONBLOCK);
if (dev_fd < 0) {
/* if retry times is greater than V4L2LENSMGR_MAX_MDEV_NUM, stop trying */
if (num_media_devices > V4L2LENSMGR_MAX_MDEV_NUM) {
CAM_LOGD("no media device anymore (at mdev %d)", num_media_devices - 1);
return -ENOENT;
}
/* if open failed, try to open the next one */
CAM_LOGD("open mdev %d failed, keep trying the next", num_media_devices);
continue;
}
/* enumerate media device info */
r = ioctl(dev_fd, MEDIA_IOC_DEVICE_INFO, &mdev_info);
if (r < 0) {
::close(dev_fd);
continue;
}
CAM_LOGD("get mdev_info.driver : %s", mdev_info.driver);
CAM_LOGD("get mdev_info.model : %s", mdev_info.model);
CAM_LOGD("get mdev_info.serial : %s", mdev_info.serial);
CAM_LOGD("get mdev_info.bus_info : %s", mdev_info.bus_info);
/* traverse all entities */
int next_entity_id = 1;
while (true) {
struct media_entity_desc entity = {0};
entity.id = next_entity_id | MEDIA_ENT_ID_FLAG_NEXT;
r = ioctl(dev_fd, MEDIA_IOC_ENUM_ENTITIES, &entity);
/* update the next entity id */
next_entity_id = entity.id;
if (r < 0) {
if (next_entity_id & MEDIA_IOC_ENUM_ENTITIES) {
/* no more entity, break loop */
break;
}
continue;
}
CAM_LOGD("entity name %s, type 0x%x, group id %d, major %d minor %d",
entity.name, entity.type, entity.group_id, entity.dev.major,
entity.dev.minor);
if (entity.type == MEDIA_ENT_T_V4L2_SUBDEV_LENS) {
/* parse i2c index by name first */
int dev_i2c_idx = getI2Cindex(entity.name);
/* checks if the i2c index is what we want */
if (dev_i2c_idx != i2c_idx) {
CAM_LOGD(
"found lens driver \"%s\", but i2c index(%d) is not "
"what we want (%d).",
entity.name, dev_i2c_idx, i2c_idx);
continue; // find the next entity
}
char __tempbuf[64] = {0};
if (0 == getSubDevName(entity.dev.major, entity.dev.minor, __tempbuf)) {
::snprintf(subdev_name, sizeof(subdev_name), "/dev/%s", __tempbuf);
sd_fd = ::open(subdev_name, O_RDWR);
if (sd_fd >= 0) {
CAM_LOGD("found lens driver %s", subdev_name);
} else {
CAM_LOGE("cannot open lens driver %s", subdev_name);
}
break;
}
}
} // while: traverse all entities of the given media device
/* if found subdev, break loop */
::close(dev_fd);
dev_fd = -1;
if (sd_fd >= 0) {
*p_r_sdev_fd = sd_fd;
break;
}
} // while: traverse media devices
if (sd_fd >= 0) {
CAM_LOGD("open lens driver %s", subdev_name);
} else {
CAM_LOGD("no lens driver to open (target i2c idx=%d)", i2c_idx);
}
return 0;
}
int V4L2LensMgr::getSubDevName(int major, int minor, char* r_subdev_name) {
char uevent_path[64] = {0};
int fd = -1, r = 0;
char* ptr = nullptr;
/* constructs file path */
::snprintf(uevent_path, sizeof(uevent_path), "/sys/dev/char/%d:%d/uevent",
major, minor);
fd = ::open(uevent_path, O_RDONLY);
if (fd < 0)
return -ENOENT;
size_t filesize = ::lseek(fd, (off_t)0, SEEK_END);
(void)::lseek(fd, (off_t)0, SEEK_SET);
if (filesize <= 0) {
close(fd);
return -ENOENT;
}
/* allocate character buffer */
std::unique_ptr<char[]> uevent = std::make_unique<char[]>(filesize + 1);
uevent[filesize] = '\0';
/* read buffer */
r = ::read(fd, uevent.get(), filesize);
close(fd);
ptr = ::strstr(uevent.get(), "DEVNAME");
if (ptr == nullptr) {
return -ENOENT;
}
r = ::sscanf(ptr, "DEVNAME=%s\n", r_subdev_name);
CAM_LOGD("subdev name is %s", r_subdev_name);
return 0;
}
int V4L2LensMgr::getI2Cindex(std::string dev_name) {
/*
* syntax is XXXXX 0-XXXXX
* ^ idx of i2c
*/
auto pos = dev_name.find_first_of("-");
/* not found or sytax not valid */
if (pos == std::string::npos || pos <= 0) {
return -1;
}
/* pick up one character before token "-" */
auto str_idx = dev_name.substr(pos - 1, 1);
/* checks character is valid (ASCII 0-9) */
if (str_idx[0] < '0' || str_idx[0] > '9') {
return -1;
}
return std::atoi(str_idx.c_str());
}
}; // namespace v4l2