Hello Guennadi,
> > Sorry for the sudden email but may I have your advice on soc_camera?
> You can have a look at another driver
> for a Sharp camera sensor:
>
> drivers/media/video/rj54n1cb0c.c
>
> and at its platform glue in arch/sh/boards/mach-kfr2r09/setup.c, there you
> find
>
> struct platform_device kfr2r09_camera
>
> which links to
>
> struct soc_camera_link rj54n1_link
> > The ARM cpu is made by Renesas.
>
> Then, perhaps, something similar to
>
> arch/arm/mach-shmobile/board-ap4evb.c
Thank you for your suggestion, I was able to bind my driver
with soc_camera(I believe...).
I attached my draft driver files at the end of this email
for any suggestions.
In the beginning, I would like to explain the fundamental
information.
1) 2 megapixel camera module is connected to
the ARM board, Renesas ag5evm, through I2C.
arch/arm/mach-shmobile/board-ag5evm.c
2) The camera module is connected to CEU on the ag5evm.
3) I followed your instruction to bind the camera
sensor driver to soc_camera as attached and
builds and boots fine.
4) I have not received the data sheet from the vender
for the camera yet ;)
5) But I have the prototype board on my hand.
6) I can not implement the details of the driver without
the data sheet but would like to start implement the
outline, so I could save my time while I am waiting
for the data sheet.
Also,
I would like to know, if I need to bind to
sh_mobile_ceu_camera.c
too, and how, because the camera is connected to CEU.
(I never knew the word CEU until I started to work with
this project...)
Thank you and best regards,
Akira
------------- rj65na20.c
/*
* Copyright (C) 2011 Nomovok Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/videodev2.h>
#include <media/v4l2-device.h>
#include <media/soc_camera.h>
#include <media/soc_mediabus.h>
MODULE_DESCRIPTION("Sharp RJ65NA20 sensor driver");
MODULE_AUTHOR("Akira Tsukamoto <[email protected]>");
MODULE_LICENSE("GPL");
static int debug;
module_param(debug, int, 1);
MODULE_PARM_DESC(debug, "Debug level (0-1)");
/*
* TODO This should go inside the following
* include/media/v4l2-chip-ident.h
*/
/* Sharp RJ65NA20, 0x???? = ?? */
#define V4L2_IDENT_RJ65NA20 00
#define I2C_WRITE_BYTES 2
#define I2C_READ_BYTES 1
#define R00_RJ65NA20_CHIP_VERSION 0x00 /* TODO fix me when after
receiving data sheet */
#define R00 0x00
#define R01 0x01
#define RJ65NA20_VERSION 0x01
#define RJ65NA20_WIDTH 1600
#define RJ65NA20_HEIGHT 1200
/* supported controls */
static struct v4l2_queryctrl rj65na20_qctrl[] = { /* TODO fix me when after
receiving data sheet */
{
.id = V4L2_CID_AUTO_WHITE_BALANCE,
.type = V4L2_CTRL_TYPE_BOOLEAN,
.name = "Auto White Balance",
.minimum = 0,
.maximum = 1,
.step = 1,
.default_value = 1,
.flags = 0,
}, {
.id = V4L2_CID_EXPOSURE_AUTO,
.type = V4L2_CTRL_TYPE_INTEGER,
.name = "Auto Exposure",
.minimum = 0,
.maximum = 1,
.step = 1,
.default_value = 1,
.flags = 0,
}, {
.id = V4L2_CID_HFLIP,
.type = V4L2_CTRL_TYPE_BOOLEAN,
.name = "Mirror",
.minimum = 0,
.maximum = 1,
.step = 1,
.default_value = 0,
.flags = 0,
}, {
.id = V4L2_CID_VFLIP,
.type = V4L2_CTRL_TYPE_BOOLEAN,
.name = "Vflip",
.minimum = 0,
.maximum = 1,
.step = 1,
.default_value = 0,
.flags = 0,
}, {
}
};
struct rj65na20 {
struct v4l2_subdev sd;
unsigned int width, height;
unsigned int autowhitebalance:1;
unsigned int autoexposure:1;
unsigned int hflip:1;
unsigned int vflip:1;
};
static inline struct rj65na20 *to_rj65na20(struct v4l2_subdev *sd)
{
return container_of(sd, struct rj65na20, sd);
}
static u8 rj65na20_read(struct v4l2_subdev *sd, u8 addr)
{
struct i2c_client *c = v4l2_get_subdevdata(sd);
u8 buffer, val;
int rc;
rc = i2c_master_send(c, &addr, I2C_READ_BYTES);
if (rc != I2C_READ_BYTES)
v4l2_dbg(0, debug, sd,
"i2c i/o error: rc == %d (should be %d)\n",
rc, I2C_READ_BYTES);
msleep(10); /* TODO specify correct value later */
rc = i2c_master_recv(c, (char *)&buffer, I2C_READ_BYTES);
if (rc != I2C_READ_BYTES)
v4l2_dbg(0, debug, sd,
"i2c i/o error: rc == %d (should be %d)\n",
rc, I2C_READ_BYTES);
val = buffer;
v4l2_dbg(2, debug, sd, "rj65na20: read 0x%02x = 0x%02x\n", addr, val);
return val;
}
static void rj65na20_write(struct v4l2_subdev *sd, u8 addr, u8 value)
{
struct i2c_client *c = v4l2_get_subdevdata(sd);
u8 buffer[I2C_WRITE_BYTES];
int rc;
buffer[0] = addr;
buffer[1] = value;
v4l2_dbg(2, debug, sd,
"rj65na20: writing 0x%02x 0x%02x\n", addr, value);
rc = i2c_master_send(c, buffer, I2C_WRITE_BYTES);
if (rc != I2C_WRITE_BYTES)
v4l2_dbg(0, debug, sd,
"i2c i/o error: rc == %d (should be %d)\n",
rc, I2C_WRITE_BYTES);
}
struct i2c_reg_value {
u8 reg;
u8 value;
};
/*
* Initialization values of registers for the device
* likly to change on final hardware
*/
#define INIT_REGS_PAGE 0x100
#define INIT_PAGES 22
static const struct i2c_reg_value rj65na20_init_page[][0x100] = {
/* TODO this is dummy values, need to fix it with correct values */
{
/* PAGE0 */
{0x00, 0x00},
{0xff, 0x00},
{
},
};
static void set_resolution(struct v4l2_subdev *sd)
{
/* TODO fix me when after receiving data sheet */
}
static void set_white_balance(struct v4l2_subdev *sd)
{
/* TODO fix me when after receiving data sheet */
}
static void set_exposure(struct v4l2_subdev *sd)
{
/* TODO fix me when after receiving data sheet */
}
static void set_flip(struct v4l2_subdev *sd)
{
/* TODO fix me when after receiving data sheet */
}
static int rj65na20_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl)
{
struct rj65na20 *core = to_rj65na20(sd);
v4l2_dbg(1, debug, sd, "g_ctrl called\n");
switch (ctrl->id) {
case V4L2_CID_AUTO_WHITE_BALANCE:
ctrl->value = core->autowhitebalance;
return 0;
case V4L2_CID_EXPOSURE_AUTO:
ctrl->value = core->autoexposure;
return 0;
case V4L2_CID_HFLIP:
ctrl->value = core->hflip ? 1 : 0;
return 0;
case V4L2_CID_VFLIP:
ctrl->value = core->vflip ? 1 : 0;
return 0;
}
return -EINVAL;
}
static int rj65na20_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl)
{
struct rj65na20 *core = to_rj65na20(sd);
u8 i, n;
int ret = -EINVAL;
n = ARRAY_SIZE(rj65na20_qctrl);
/* varify args */
for (i = 0; i < n; i++) {
if (ctrl->id != rj65na20_qctrl[i].id)
continue;
if (ctrl->value < rj65na20_qctrl[i].minimum ||
ctrl->value > rj65na20_qctrl[i].maximum)
return -ERANGE;
v4l2_dbg(1, debug, sd, "s_ctrl: id=%d, value=%d\n",
ctrl->id, ctrl->value);
}
switch (ctrl->id) {
case V4L2_CID_AUTO_WHITE_BALANCE:
core->autowhitebalance = ctrl->value;
set_white_balance(sd);
return 0;
case V4L2_CID_EXPOSURE_AUTO:
core->autoexposure = ctrl->value;
set_exposure(sd);
return 0;
case V4L2_CID_HFLIP:
core->hflip = ctrl->value;
set_flip(sd);
return 0;
case V4L2_CID_VFLIP:
core->vflip = ctrl->value;
set_flip(sd);
return 0;
default:
return -EINVAL;
}
return ret;
}
#if 0 /* TODO does the camera need to specify colorspace? */
static int rj65na20_try_fmt(struct v4l2_subdev *sd,
struct v4l2_mbus_framefmt *mf)
{
/* TODO fix me when after receiving data sheet */
const struct rj65na20_datafmt *fmt = rj65na20_find_datafmt(mf->code);
v4l2_dbg(1, debug, sd, "rj65na20_try_fmt called: %u\n", mf->code);
if (!fmt) {
mf->code = rj65na20_colour_fmts[0].code;
mf->colorspace = rj65na20_colour_fmts[0].colorspace;
}
mf->width = RJ65NA20_WIDTH;
mf->height = RJ65NA20_HEIGHT;
mf->field = V4L2_FIELD_NONE;
return 0;
}
static int rj65na20_s_fmt(struct v4l2_subdev *sd,
struct v4l2_mbus_framefmt *mf)
{
/* TODO fix me when after receiving data sheet */
struct rj65na20 *core = to_rj65na20(sd);
v4l2_dbg(1, debug, sd, "rj65na20_s_fmt called: %u\n", mf->code);
if (!rj65na20_find_datafmt(mf->code))
return -EINVAL;
rj65na20_try_fmt(sd, mf);
core->fmt = rj65na20_find_datafmt(mf->code);
return 0;
}
static int rj65na20_g_fmt(struct v4l2_subdev *sd,
struct v4l2_mbus_framefmt *mf)
{
/* TODO fix me when after receiving data sheet */
struct rj65na20 *core = to_rj65na20(sd);
const struct rj65na20_datafmt *fmt = core->fmt;
mf->code = fmt->code;
mf->colorspace = fmt->colorspace;
mf->width = RJ65NA20_WIDTH;
mf->height = RJ65NA20_HEIGHT;
mf->field = V4L2_FIELD_NONE;
return 0;
}
#endif
static int rj65na20_g_crop(struct v4l2_subdev *sd, struct v4l2_crop *a)
{
/* TODO fix me when after receiving data sheet */
struct v4l2_rect *rect = &a->c;
a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
rect->top = 0;
rect->left = 0;
rect->width = RJ65NA20_WIDTH;
rect->height = RJ65NA20_HEIGHT;
return 0;
}
static int rj65na20_cropcap(struct v4l2_subdev *sd, struct v4l2_cropcap *a)
{
/* TODO fix me when after receiving data sheet */
a->bounds.left = 0;
a->bounds.top = 0;
a->bounds.width = RJ65NA20_WIDTH;
a->bounds.height = RJ65NA20_HEIGHT;
a->defrect = a->bounds;
a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
a->pixelaspect.numerator = 1;
a->pixelaspect.denominator = 1;
return 0;
}
static int rj65na20_enum_fmt(struct v4l2_subdev *sd, unsigned int index,
enum v4l2_mbus_pixelcode *code)
{
/* TODO fix me when after receiving data sheet */
/*
if ((unsigned int)index >= ARRAY_SIZE(rj65na20_colour_fmts))
return -EINVAL;
*code = rj65na20_colour_fmts[index].code;
*/
return 0;
}
static int rj65na20_s_stream(struct v4l2_subdev *sd, int enable)
{
/* TODO fix me when after receiving data sheet */
// struct i2c_client *client = v4l2_get_subdevdata(sd);
// return rj65na20_write(client, MODE_SELECT, !!enable);
return 0;
}
static int rj65na20_g_chip_ident(struct v4l2_subdev *sd,
struct v4l2_dbg_chip_ident *id)
{
struct i2c_client *client = v4l2_get_subdevdata(sd);
if (id->match.type != V4L2_CHIP_MATCH_I2C_ADDR)
return -EINVAL;
if (id->match.addr != client->addr)
return -ENODEV;
id->ident = V4L2_IDENT_RJ65NA20;
id->revision = 0;
return 0;
}
static struct v4l2_subdev_video_ops rj65na20_subdev_video_ops = {
.s_stream = rj65na20_s_stream,
// .s_mbus_fmt = rj65na20_s_fmt,
// .g_mbus_fmt = rj65na20_g_fmt,
// .try_mbus_fmt = rj65na20_try_fmt,
.enum_mbus_fmt = rj65na20_enum_fmt,
.g_crop = rj65na20_g_crop,
.cropcap = rj65na20_cropcap,
};
static struct v4l2_subdev_core_ops rj65na20_subdev_core_ops = {
.g_chip_ident = rj65na20_g_chip_ident,
};
static struct v4l2_subdev_ops rj65na20_subdev_ops = {
.core = &rj65na20_subdev_core_ops,
.video = &rj65na20_subdev_video_ops,
};
/* The camera is connected through CEU */
static unsigned long rj65na20_query_bus_param(struct soc_camera_device *icd)
{
return 0;
}
static int rj65na20_set_bus_param(struct soc_camera_device *icd,
unsigned long flags)
{
return -1;
}
static struct soc_camera_ops rj65na20_ops = {
.query_bus_param = rj65na20_query_bus_param,
.set_bus_param = rj65na20_set_bus_param,
};
/****************************************************************************
I2C Client & Driver
****************************************************************************/
/*
* This is i2c testing purpose,
* this function will be removed from final code
*/
static int rj65na20_probe_test(struct i2c_client *c,
const struct i2c_device_id *id)
{
struct rj65na20 *core;
struct v4l2_subdev *sd;
int rc;
u8 addr, value, read;
u8 write[I2C_WRITE_BYTES];
printk("APE5R: 2M camera probe: start\n");
sd = &core->sd;
/* Check if the adapter supports the needed features */
if (!i2c_check_functionality(c->adapter,
I2C_FUNC_SMBUS_READ_BYTE | I2C_FUNC_SMBUS_WRITE_BYTE_DATA))
return -EIO;
addr = 0x03;
value = 0x01;
printk("APE5R: 2M camera probe: write before\n");
write[0] = addr;
write[1] = value;
v4l2_dbg(2, debug, sd,
"rj65na20: writing 0x%02x 0x%04x\n", addr, value);
rc = i2c_master_send(c, write, I2C_WRITE_BYTES);
if (rc != I2C_WRITE_BYTES)
v4l2_dbg(0, debug, sd,
"i2c i/o error: rc == %d (should be %d)\n",
rc, I2C_WRITE_BYTES);
printk("APE5R: 2M camera probe: write after\n");
rc = i2c_master_send(c, &addr, I2C_READ_BYTES);
if (rc != I2C_READ_BYTES)
v4l2_dbg(0, debug, sd,
"i2c i/o error: rc == %d (should be %d)\n",
rc, I2C_READ_BYTES);
msleep(10);
printk("APE5R: 2M camera probe: read send addr\n");
rc = i2c_master_recv(c, (char *)&read, I2C_READ_BYTES);
if (rc != I2C_READ_BYTES)
v4l2_dbg(0, debug, sd,
"i2c i/o error: rc == %d (should be %d)\n",
rc, I2C_READ_BYTES);
printk("APE5R: 2M camera probe: read=0x%02X\n", read);
printk("APE5R: 2M camera probe: end\n");
// BUG_ON(__func__); /* halt the testing */
return 0;
}
static int rj65na20_probe(struct i2c_client *c,
const struct i2c_device_id *id)
{
u8 version;
struct rj65na20 *core;
struct v4l2_subdev *sd;
struct soc_camera_device *icd = c->dev.platform_data;
struct soc_camera_link *icl;
if (!icd) {
dev_err(&c->dev, "rj65na20: missing soc-camera data!\n");
return -EINVAL;
}
icl = to_soc_camera_link(icd);
if (!icl) {
dev_err(&c->dev, "rj65na20: missing platform data!\n");
return -EINVAL;
}
/* Check if the adapter supports the needed i2c features */
if (!i2c_check_functionality(c->adapter,
I2C_FUNC_SMBUS_READ_BYTE | I2C_FUNC_SMBUS_WRITE_BYTE_DATA))
return -EIO;
core = kzalloc(sizeof(struct rj65na20), GFP_KERNEL);
if (!core)
return -ENOMEM;
sd = &core->sd;
v4l2_i2c_subdev_init(sd, c, &rj65na20_subdev_ops);
/* Check if the sensor is really a Sharp RJ65NA20 */
version = rj65na20_read(sd, 0x01);
if ((version != RJ65NA20_VERSION)) {
v4l2_info(sd, "*** unknown camera chip detected (0x%02x).\n",
version);
kfree(core);
return -EINVAL;
}
icd->ops = &rj65na20_ops;
core->width = RJ65NA20_WIDTH;
core->height = RJ65NA20_HEIGHT;
v4l_info(c, "chip found @ 0x%02x (%s - chip version 0x%04x)\n",
c->addr << 1, c->adapter->name, version);
return 0;
}
static int rj65na20_remove(struct i2c_client *c)
{
struct v4l2_subdev *sd = i2c_get_clientdata(c);
v4l2_dbg(1, debug, sd,
"rj65na20.c: removing rj65na20 adapter on address 0x%x\n",
c->addr << 1);
v4l2_device_unregister_subdev(sd);
kfree(to_rj65na20(sd));
return 0;
}
/* ----------------------------------------------------------------------- */
static const struct i2c_device_id rj65na20_id[] = {
{ "rj65na20", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, rj65na20_id);
static struct i2c_driver rj65na20_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "rj65na20",
},
.probe = rj65na20_probe_test,
.remove = rj65na20_remove,
.id_table = rj65na20_id,
};
static __init int init_rj65na20(void)
{
return i2c_add_driver(&rj65na20_driver);
}
static __exit void exit_rj65na20(void)
{
i2c_del_driver(&rj65na20_driver);
}
module_init(init_rj65na20);
module_exit(exit_rj65na20);
EOF (rj65na20.c)
-----------------------------
--- linux_kernel_bsp/arch/arm/mach-shmobile/board-ag5evm.c 2011-03-22
12:30:14.000000000 +0900
+++ linux_kernel/arch/arm/mach-shmobile/board-ag5evm.c 2011-04-18
14:39:20.000000000 +0900
@@ -59,6 +59,7 @@
#include <sound/sh_fsi.h>
#include <video/sh_mobile_lcdc.h>
+#include <media/soc_camera.h>
static struct r8a66597_platdata usb_host_data = {
.on_chip = 1,
@@ -317,11 +318,38 @@ static struct platform_device fsi_device
},
};
+static struct i2c_board_info rj65na20_info = {
+ I2C_BOARD_INFO("rj65na20", 0x40),
+};
+
+struct soc_camera_link rj65na20_link = {
+ .bus_id = 0,
+ .board_info = &rj65na20_info,
+ .i2c_adapter_id = 0,
+ .module_name = "rj65na20",
+};
+
+static struct platform_device rj65na20_camera = {
+ .name = "soc-camera-pdrv-2M",
+ .id = 0,
+ .dev = {
+ .platform_data = &rj65na20_link,
+ },
+};
+
static struct i2c_board_info i2c0_devices[] = {
{
I2C_BOARD_INFO("ag5evm_ts", 0x20),
.irq = pint2irq(12), /* PINTC3 */
},
+ /* 2M camera */
+ {
+ I2C_BOARD_INFO("rj65na20", 0x40),
+ },
};
static struct i2c_board_info i2c1_devices[] = {
@@ -548,6 +576,8 @@ static struct platform_device *ag5evm_de
&usb_mass_storage_device,
&android_usb_device,
+
+ &rj65na20_camera,
};
static struct map_desc ag5evm_io_desc[] __initdata = {
@@ -748,6 +778,7 @@ static void __init ag5evm_init(void)
struct clk *sub_clk = clk_get(NULL, "sub_clk");
struct clk *extal2_clk = clk_get(NULL, "extal2");
struct clk *fsia_clk = clk_get(NULL, "fsia_clk");
+ struct clk *vck1_clk = clk_get(NULL, "vck1_clk");
clk_set_parent(sub_clk, extal2_clk);
__raw_writel(__raw_readl(SUBCKCR) & ~(1<<9), SUBCKCR);
@@ -853,6 +884,43 @@ static void __init ag5evm_init(void)
__raw_writel(0x2a8b9111, DSI1PHYCR);
clk_enable(clk_get(NULL, "dsi-tx"));
+ /* 2M camera */
+ gpio_request(GPIO_PORT44, NULL);
+ gpio_direction_output(GPIO_PORT44, 0);
+ udelay(10);
+ gpio_set_value(GPIO_PORT44, 1);
+
/* Unreset LCD Panel */
gpio_request(GPIO_PORT217, NULL);
gpio_direction_output(GPIO_PORT217, 0);
--
Akira Tsukamoto
--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html