blob: 74672494455caf78aa1b4ad1d483b0e88a725a10 [file] [log] [blame]
/*
* Copyright The Zephyr Project Contributors
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT himax_hm01b0
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/video-controls.h>
#include <zephyr/drivers/video.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/util.h>
#include "video_device.h"
#include "video_common.h"
LOG_MODULE_REGISTER(hm01b0, CONFIG_VIDEO_LOG_LEVEL);
#define MAX_FRAME_RATE 10
#define MIN_FRAME_RATE 1
#define HM01B0_ID 0x01B0
#define HM01B0_LINE_LEN_PCLK_IDX 5
#define HM01B0_REG8(addr) ((addr) | VIDEO_REG_ADDR16_DATA8)
#define HM01B0_REG16(addr) ((addr) | VIDEO_REG_ADDR16_DATA16_BE)
#define HM01B0_CCI_ID HM01B0_REG16(0x0000)
#define HM01B0_CCI_STS HM01B0_REG8(0x0100)
#define HM01B0_CCI_RESET HM01B0_REG8(0x0103)
#define HM01B0_CCI_GRP_PARAM_HOLD HM01B0_REG8(0x0104)
#define HM01B0_CCI_INTEGRATION_H HM01B0_REG16(0x0202)
#define HM01B0_CCI_FRAME_LENGTH_LINES HM01B0_REG16(0x0340)
#define HM01B0_CCI_LINE_LENGTH_PCLK HM01B0_REG16(0x0342)
#define HM01B0_CCI_WIDTH HM01B0_REG8(0x0383)
#define HM01B0_CCI_HEIGHT HM01B0_REG8(0x0387)
#define HM01B0_CCI_BINNING_MODE HM01B0_REG8(0x0390)
#define HM01B0_CCI_QVGA_WIN_EN HM01B0_REG8(0x3010)
#define HM01B0_CCI_BIT_CONTROL HM01B0_REG8(0x3059)
#define HM01B0_CCI_OSC_CLOCK_DIV HM01B0_REG8(0x3060)
#define HM01B0_CTRL_VAL(data_bits) \
((data_bits) == 8 ? 0x02 : \
(data_bits) == 4 ? 0x42 : \
(data_bits) == 1 ? 0x22 : 0x00)
enum hm01b0_resolution {
RESOLUTION_160x120,
RESOLUTION_320x240,
RESOLUTION_320x320,
};
struct video_reg hm01b0_160x120_regs[] = {
{HM01B0_CCI_WIDTH, 0x3},
{HM01B0_CCI_HEIGHT, 0x3},
{HM01B0_CCI_BINNING_MODE, 0x3},
{HM01B0_CCI_QVGA_WIN_EN, 0x1},
{HM01B0_CCI_FRAME_LENGTH_LINES, 0x80},
{HM01B0_CCI_LINE_LENGTH_PCLK, 0xD7},
};
struct video_reg hm01b0_320x240_regs[] = {
{HM01B0_CCI_WIDTH, 0x1},
{HM01B0_CCI_HEIGHT, 0x1},
{HM01B0_CCI_BINNING_MODE, 0x0},
{HM01B0_CCI_QVGA_WIN_EN, 0x1},
{HM01B0_CCI_FRAME_LENGTH_LINES, 0x104},
{HM01B0_CCI_LINE_LENGTH_PCLK, 0x178},
};
struct video_reg hm01b0_320x320_regs[] = {
{HM01B0_CCI_WIDTH, 0x1},
{HM01B0_CCI_HEIGHT, 0x1},
{HM01B0_CCI_BINNING_MODE, 0x0},
{HM01B0_CCI_QVGA_WIN_EN, 0x0},
{HM01B0_CCI_FRAME_LENGTH_LINES, 0x158},
{HM01B0_CCI_LINE_LENGTH_PCLK, 0x178},
};
struct video_reg *hm01b0_init_regs[] = {
[RESOLUTION_160x120] = hm01b0_160x120_regs,
[RESOLUTION_320x240] = hm01b0_320x240_regs,
[RESOLUTION_320x320] = hm01b0_320x320_regs,
};
struct hm01b0_data {
struct video_format fmt;
};
struct hm01b0_config {
const struct i2c_dt_spec i2c;
const uint8_t data_bits;
const uint8_t ctrl_val;
};
#define HM01B0_VIDEO_FORMAT_CAP(width, height, format) \
{ \
.pixelformat = (format), \
.width_min = (width), \
.width_max = (width), \
.height_min = (height), \
.height_max = (height), \
.width_step = 0, \
.height_step = 0, \
}
static const struct video_format_cap hm01b0_fmts[] = {
HM01B0_VIDEO_FORMAT_CAP(160, 120, VIDEO_PIX_FMT_GREY),
HM01B0_VIDEO_FORMAT_CAP(320, 240, VIDEO_PIX_FMT_GREY),
HM01B0_VIDEO_FORMAT_CAP(320, 320, VIDEO_PIX_FMT_GREY),
{0},
};
static int hm01b0_apply_configuration(const struct device *dev, enum hm01b0_resolution resolution)
{
const struct hm01b0_config *config = dev->config;
int ret;
/* Number of registers is the same for all configuration */
ret = video_write_cci_multiregs(&config->i2c, hm01b0_init_regs[resolution],
ARRAY_SIZE(hm01b0_160x120_regs));
if (ret < 0) {
LOG_ERR("Failed to write config list registers (%d)", ret);
return ret;
}
/* REG_BIT_CONTROL */
ret = video_write_cci_reg(&config->i2c, HM01B0_CCI_BIT_CONTROL, config->ctrl_val);
if (ret < 0) {
LOG_ERR("Failed to write BIT_CONTROL reg (%d)", ret);
return ret;
}
/* OSC_CLK_DIV */
ret = video_write_cci_reg(&config->i2c, HM01B0_CCI_OSC_CLOCK_DIV, 0x08);
if (ret < 0) {
LOG_ERR("Failed to write OSC_CLK_DIV reg (%d)", ret);
return ret;
}
/* INTEGRATION_H */
ret = video_write_cci_reg(&config->i2c, HM01B0_CCI_INTEGRATION_H,
hm01b0_init_regs[resolution][HM01B0_LINE_LEN_PCLK_IDX].data / 2);
if (ret < 0) {
LOG_ERR("Failed to write INTEGRATION_H reg (%d)", ret);
return ret;
}
/* GRP_PARAM_HOLD */
ret = video_write_cci_reg(&config->i2c, HM01B0_CCI_GRP_PARAM_HOLD, 0x01);
if (ret < 0) {
LOG_ERR("Failed to write GRP_PARAM_HOLD reg (%d)", ret);
return ret;
}
return ret;
}
static int hm01b0_get_caps(const struct device *dev, struct video_caps *caps)
{
caps->format_caps = hm01b0_fmts;
return 0;
}
static int hm01b0_set_fmt(const struct device *dev, struct video_format *fmt)
{
struct hm01b0_data *data = dev->data;
size_t idx;
int ret;
LOG_DBG("HM01B0 set_fmt: %d x %d, fmt: %s", fmt->width, fmt->height,
VIDEO_FOURCC_TO_STR(fmt->pixelformat));
ret = video_format_caps_index(hm01b0_fmts, fmt, &idx);
if (ret != 0) {
LOG_ERR("Image resolution not supported\n");
return ret;
}
if (!memcmp(&data->fmt, fmt, sizeof(data->fmt))) {
return 0;
}
/* Check if camera is capable of handling given format */
ret = hm01b0_apply_configuration(dev, (enum hm01b0_resolution)idx);
if (ret != 0) {
/* Camera is not capable of handling given format */
LOG_ERR("Image resolution not supported");
return ret;
}
data->fmt = *fmt;
return ret;
}
static int hm01b0_get_fmt(const struct device *dev, struct video_format *fmt)
{
struct hm01b0_data *data = dev->data;
*fmt = data->fmt;
LOG_DBG("HM01B0 get_fmt: %d x %d, fmt: %s", fmt->width, fmt->height,
VIDEO_FOURCC_TO_STR(fmt->pixelformat));
return 0;
}
static int hm01b0_set_stream(const struct device *dev, bool enable, enum video_buf_type type)
{
const struct hm01b0_config *config = dev->config;
/* SET MODE_SELECT */
return video_write_cci_reg(&config->i2c, HM01B0_CCI_STS, enable ? 1 : 0);
}
static int hm01b0_soft_reset(const struct device *dev)
{
const struct hm01b0_config *config = dev->config;
uint32_t val = 0xff;
int ret;
ret = video_write_cci_reg(&config->i2c, HM01B0_CCI_RESET, 0x01);
if (ret < 0) {
LOG_ERR("Error writing HM01B0_CCI_RESET (%d)", ret);
return ret;
}
for (int retries = 0; retries < 10; retries++) {
ret = video_read_cci_reg(&config->i2c, HM01B0_CCI_STS, &val);
if (ret != 0 || val == 0x0) {
break;
}
k_msleep(100);
}
if (ret != 0) {
LOG_ERR("Soft reset error (%d)", ret);
}
return ret;
}
static DEVICE_API(video, hm01b0_driver_api) = {
.set_format = hm01b0_set_fmt,
.get_format = hm01b0_get_fmt,
.set_stream = hm01b0_set_stream,
.get_caps = hm01b0_get_caps,
};
static bool hm01b0_check_connection(const struct device *dev)
{
const struct hm01b0_config *config = dev->config;
uint32_t model_id;
int ret;
ret = video_read_cci_reg(&config->i2c, HM01B0_CCI_ID, &model_id);
if (ret < 0) {
LOG_ERR("Error reading id reg (%d)", ret);
return false;
}
return (model_id == HM01B0_ID);
}
static int hm01b0_init(const struct device *dev)
{
int ret;
if (!hm01b0_check_connection(dev)) {
LOG_ERR("%s is not ready", dev->name);
return -ENODEV;
}
ret = hm01b0_soft_reset(dev);
if (ret != 0) {
LOG_ERR("error soft reset (%d)", ret);
return ret;
}
struct video_format fmt = {
.pixelformat = VIDEO_PIX_FMT_GREY,
.width = 160,
.height = 120,
.type = VIDEO_BUF_TYPE_OUTPUT,
};
ret = hm01b0_set_fmt(dev, &fmt);
if (ret != 0) {
LOG_ERR("Error setting video format (%d)", ret);
return ret;
}
return 0;
}
#define HM01B0_INIT(inst) \
const struct hm01b0_config hm01b0_config_##inst = { \
.i2c = I2C_DT_SPEC_INST_GET(inst), \
.data_bits = DT_INST_PROP(inst, data_bits), \
.ctrl_val = HM01B0_CTRL_VAL(DT_INST_PROP(inst, data_bits)), \
}; \
struct hm01b0_data hm01b0_data_##inst; \
DEVICE_DT_INST_DEFINE(inst, &hm01b0_init, NULL, &hm01b0_data_##inst, \
&hm01b0_config_##inst, POST_KERNEL, CONFIG_VIDEO_INIT_PRIORITY, \
&hm01b0_driver_api); \
VIDEO_DEVICE_DEFINE(hm01b0_##inst, DEVICE_DT_INST_GET(inst), NULL);
DT_INST_FOREACH_STATUS_OKAY(HM01B0_INIT)