Skip to content

Commit

Permalink
UPSTREAM: media: dw9719: Add DW9761 support
Browse files Browse the repository at this point in the history
Add support for the DW9761 VCM controller, which is very similar to
the DW9719.

The new support is based on
drivers/external_drivers/camera/drivers/media/i2c/micam/dw9761.c
from the Xiaomi kernel sources for the Mi Pad 2.

The DW9761 support has been tested on a Xiaomi Mi Pad 2 tablet and
DW9719 support has been tested (to avoid regressions) on a Microsoft
Surface Go tablet.

Link: https://github.com/MiCode/Xiaomi_Kernel_OpenSource/
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Tested-by: André Apitzsch <git@apitzsch.eu>
Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com>
  • Loading branch information
jwrdegoede authored and TravMurav committed Feb 15, 2025
1 parent d3d1343 commit a241cac
Showing 1 changed file with 71 additions and 42 deletions.
113 changes: 71 additions & 42 deletions drivers/media/i2c/dw9719.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
// Copyright (c) 2012 Intel Corporation

/*
* Based on linux/modules/camera/drivers/media/i2c/imx/dw9719.c in this repo:
* https://github.com/ZenfoneArea/android_kernel_asus_zenfone5
* Based on linux/modules/camera/drivers/media/i2c/imx/dw9719.c from:
* https://github.com/ZenfoneArea/android_kernel_asus_zenfone5 and
* latte-l-oss/drivers/external_drivers/camera/drivers/media/i2c/micam/dw9761.c
* from: https://github.com/MiCode/Xiaomi_Kernel_OpenSource/
*/

#include <linux/delay.h>
Expand All @@ -23,26 +25,45 @@

#define DW9719_INFO CCI_REG8(0)
#define DW9719_ID 0xF1
#define DW9761_ID 0xF4

#define DW9719_CONTROL CCI_REG8(2)
#define DW9719_STANDBY 0x00
#define DW9719_SHUTDOWN 0x01
#define DW9719_ENABLE_RINGING 0x02

#define DW9719_VCM_CURRENT CCI_REG16(3)

#define DW9719_STATUS CCI_REG16(5)
#define DW9719_STATUS_BUSY BIT(0)

#define DW9719_MODE CCI_REG8(6)
#define DW9719_MODE_SAC_SHIFT 4
#define DW9719_MODE_SAC3 4
#define DW9719_DEFAULT_SAC 4
#define DW9761_DEFAULT_SAC 6

#define DW9719_VCM_FREQ CCI_REG8(7)
#define DW9719_DEFAULT_VCM_FREQ 0x60
#define DW9761_DEFAULT_VCM_FREQ 0x3E

#define DW9761_VCM_PRELOAD CCI_REG8(8)
#define DW9761_DEFAULT_VCM_PRELOAD 0x73


#define to_dw9719_device(x) container_of(x, struct dw9719_device, sd)

enum dw9719_model {
DW9719,
DW9761,
};

struct dw9719_device {
struct v4l2_subdev sd;
struct device *dev;
struct regmap *regmap;
struct regulator *regulator;
enum dw9719_model model;
u32 mode_low_bits;
u32 sac_mode;
u32 vcm_freq;

Expand All @@ -52,47 +73,69 @@ struct dw9719_device {
} ctrls;
};

static int dw9719_detect(struct dw9719_device *dw9719)
{
int ret;
u64 val;

ret = cci_read(dw9719->regmap, DW9719_INFO, &val, NULL);
if (ret < 0)
return ret;

if (val != DW9719_ID) {
dev_err(dw9719->dev, "Failed to detect correct id\n");
return -ENXIO;
}

return 0;
}

static int dw9719_power_down(struct dw9719_device *dw9719)
{
return regulator_disable(dw9719->regulator);
}

static int dw9719_power_up(struct dw9719_device *dw9719)
static int dw9719_power_up(struct dw9719_device *dw9719, bool detect)
{
u64 val;
int ret;

ret = regulator_enable(dw9719->regulator);
if (ret)
return ret;

/* Jiggle SCL pin to wake up device */
cci_write(dw9719->regmap, DW9719_CONTROL, 1, &ret);

cci_write(dw9719->regmap, DW9719_CONTROL, DW9719_SHUTDOWN, &ret);
fsleep(100);
cci_write(dw9719->regmap, DW9719_CONTROL, DW9719_STANDBY, &ret);
/* Need 100us to transit from SHUTDOWN to STANDBY */
fsleep(100);

if (detect) {
ret = cci_read(dw9719->regmap, DW9719_INFO, &val, NULL);
if (ret < 0)
return ret;

switch (val) {
case DW9719_ID:
dw9719->model = DW9719;
dw9719->mode_low_bits = 0x00;
dw9719->sac_mode = DW9719_DEFAULT_SAC;
dw9719->vcm_freq = DW9719_DEFAULT_VCM_FREQ;
break;
case DW9761_ID:
dw9719->model = DW9761;
dw9719->mode_low_bits = 0x01;
dw9719->sac_mode = DW9761_DEFAULT_SAC;
dw9719->vcm_freq = DW9761_DEFAULT_VCM_FREQ;
break;
default:
dev_err(dw9719->dev,
"Error unknown device id 0x%02llx\n", val);
return -ENXIO;
}

/* Optional indication of SAC mode select */
device_property_read_u32(dw9719->dev, "dongwoon,sac-mode",
&dw9719->sac_mode);

/* Optional indication of VCM frequency */
device_property_read_u32(dw9719->dev, "dongwoon,vcm-freq",
&dw9719->vcm_freq);
}

cci_write(dw9719->regmap, DW9719_CONTROL, DW9719_ENABLE_RINGING, &ret);
cci_write(dw9719->regmap, DW9719_MODE,
dw9719->sac_mode << DW9719_MODE_SAC_SHIFT, &ret);
cci_write(dw9719->regmap, DW9719_MODE, dw9719->mode_low_bits |
(dw9719->sac_mode << DW9719_MODE_SAC_SHIFT), &ret);
cci_write(dw9719->regmap, DW9719_VCM_FREQ, dw9719->vcm_freq, &ret);

if (dw9719->model == DW9761)
cci_write(dw9719->regmap, DW9761_VCM_PRELOAD,
DW9761_DEFAULT_VCM_PRELOAD, &ret);

if (ret)
dw9719_power_down(dw9719);

Expand Down Expand Up @@ -159,7 +202,7 @@ static int dw9719_resume(struct device *dev)
int ret;
int val;

ret = dw9719_power_up(dw9719);
ret = dw9719_power_up(dw9719, false);
if (ret)
return ret;

Expand Down Expand Up @@ -237,16 +280,6 @@ static int dw9719_probe(struct i2c_client *client)
return PTR_ERR(dw9719->regmap);

dw9719->dev = &client->dev;
dw9719->sac_mode = DW9719_MODE_SAC3;
dw9719->vcm_freq = DW9719_DEFAULT_VCM_FREQ;

/* Optional indication of SAC mode select */
device_property_read_u32(&client->dev, "dongwoon,sac-mode",
&dw9719->sac_mode);

/* Optional indication of VCM frequency */
device_property_read_u32(&client->dev, "dongwoon,vcm-freq",
&dw9719->vcm_freq);

dw9719->regulator = devm_regulator_get(&client->dev, "vdd");
if (IS_ERR(dw9719->regulator))
Expand Down Expand Up @@ -274,14 +307,10 @@ static int dw9719_probe(struct i2c_client *client)
* will work.
*/

ret = dw9719_power_up(dw9719);
ret = dw9719_power_up(dw9719, true);
if (ret)
goto err_cleanup_media;

ret = dw9719_detect(dw9719);
if (ret)
goto err_powerdown;

pm_runtime_set_active(&client->dev);
pm_runtime_get_noresume(&client->dev);
pm_runtime_enable(&client->dev);
Expand All @@ -299,7 +328,6 @@ static int dw9719_probe(struct i2c_client *client)
err_pm_runtime:
pm_runtime_disable(&client->dev);
pm_runtime_put_noidle(&client->dev);
err_powerdown:
dw9719_power_down(dw9719);
err_cleanup_media:
media_entity_cleanup(&dw9719->sd.entity);
Expand Down Expand Up @@ -327,6 +355,7 @@ static void dw9719_remove(struct i2c_client *client)

static const struct i2c_device_id dw9719_id_table[] = {
{ "dw9719" },
{ "dw9761" },
{ }
};
MODULE_DEVICE_TABLE(i2c, dw9719_id_table);
Expand Down

0 comments on commit a241cac

Please sign in to comment.