in i2c/ccs/ccs-core.c [3282:3666]
static int ccs_probe(struct i2c_client *client)
{
struct ccs_sensor *sensor;
const struct firmware *fw;
char filename[40];
unsigned int i;
int rval;
sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL);
if (sensor == NULL)
return -ENOMEM;
rval = ccs_get_hwconfig(sensor, &client->dev);
if (rval)
return rval;
sensor->src = &sensor->ssds[sensor->ssds_used];
v4l2_i2c_subdev_init(&sensor->src->sd, client, &ccs_ops);
sensor->src->sd.internal_ops = &ccs_internal_src_ops;
sensor->regulators = devm_kcalloc(&client->dev,
ARRAY_SIZE(ccs_regulators),
sizeof(*sensor->regulators),
GFP_KERNEL);
if (!sensor->regulators)
return -ENOMEM;
for (i = 0; i < ARRAY_SIZE(ccs_regulators); i++)
sensor->regulators[i].supply = ccs_regulators[i];
rval = devm_regulator_bulk_get(&client->dev, ARRAY_SIZE(ccs_regulators),
sensor->regulators);
if (rval) {
dev_err(&client->dev, "could not get regulators\n");
return rval;
}
sensor->ext_clk = devm_clk_get(&client->dev, NULL);
if (PTR_ERR(sensor->ext_clk) == -ENOENT) {
dev_info(&client->dev, "no clock defined, continuing...\n");
sensor->ext_clk = NULL;
} else if (IS_ERR(sensor->ext_clk)) {
dev_err(&client->dev, "could not get clock (%ld)\n",
PTR_ERR(sensor->ext_clk));
return -EPROBE_DEFER;
}
if (sensor->ext_clk) {
if (sensor->hwcfg.ext_clk) {
unsigned long rate;
rval = clk_set_rate(sensor->ext_clk,
sensor->hwcfg.ext_clk);
if (rval < 0) {
dev_err(&client->dev,
"unable to set clock freq to %u\n",
sensor->hwcfg.ext_clk);
return rval;
}
rate = clk_get_rate(sensor->ext_clk);
if (rate != sensor->hwcfg.ext_clk) {
dev_err(&client->dev,
"can't set clock freq, asked for %u but got %lu\n",
sensor->hwcfg.ext_clk, rate);
return -EINVAL;
}
} else {
sensor->hwcfg.ext_clk = clk_get_rate(sensor->ext_clk);
dev_dbg(&client->dev, "obtained clock freq %u\n",
sensor->hwcfg.ext_clk);
}
} else if (sensor->hwcfg.ext_clk) {
dev_dbg(&client->dev, "assuming clock freq %u\n",
sensor->hwcfg.ext_clk);
} else {
dev_err(&client->dev, "unable to obtain clock freq\n");
return -EINVAL;
}
if (!sensor->hwcfg.ext_clk) {
dev_err(&client->dev, "cannot work with xclk frequency 0\n");
return -EINVAL;
}
sensor->reset = devm_gpiod_get_optional(&client->dev, "reset",
GPIOD_OUT_HIGH);
if (IS_ERR(sensor->reset))
return PTR_ERR(sensor->reset);
/* Support old users that may have used "xshutdown" property. */
if (!sensor->reset)
sensor->xshutdown = devm_gpiod_get_optional(&client->dev,
"xshutdown",
GPIOD_OUT_LOW);
if (IS_ERR(sensor->xshutdown))
return PTR_ERR(sensor->xshutdown);
rval = ccs_power_on(&client->dev);
if (rval < 0)
return rval;
mutex_init(&sensor->mutex);
rval = ccs_identify_module(sensor);
if (rval) {
rval = -ENODEV;
goto out_power_off;
}
rval = snprintf(filename, sizeof(filename),
"ccs/ccs-sensor-%4.4x-%4.4x-%4.4x.fw",
sensor->minfo.sensor_mipi_manufacturer_id,
sensor->minfo.sensor_model_id,
sensor->minfo.sensor_revision_number);
if (rval >= sizeof(filename)) {
rval = -ENOMEM;
goto out_power_off;
}
rval = request_firmware(&fw, filename, &client->dev);
if (!rval) {
ccs_data_parse(&sensor->sdata, fw->data, fw->size, &client->dev,
true);
release_firmware(fw);
}
rval = snprintf(filename, sizeof(filename),
"ccs/ccs-module-%4.4x-%4.4x-%4.4x.fw",
sensor->minfo.mipi_manufacturer_id,
sensor->minfo.model_id,
sensor->minfo.revision_number);
if (rval >= sizeof(filename)) {
rval = -ENOMEM;
goto out_release_sdata;
}
rval = request_firmware(&fw, filename, &client->dev);
if (!rval) {
ccs_data_parse(&sensor->mdata, fw->data, fw->size, &client->dev,
true);
release_firmware(fw);
}
rval = ccs_read_all_limits(sensor);
if (rval)
goto out_release_mdata;
rval = ccs_read_frame_fmt(sensor);
if (rval) {
rval = -ENODEV;
goto out_free_ccs_limits;
}
rval = ccs_update_phy_ctrl(sensor);
if (rval < 0)
goto out_free_ccs_limits;
/*
* Handle Sensor Module orientation on the board.
*
* The application of H-FLIP and V-FLIP on the sensor is modified by
* the sensor orientation on the board.
*
* For CCS_BOARD_SENSOR_ORIENT_180 the default behaviour is to set
* both H-FLIP and V-FLIP for normal operation which also implies
* that a set/unset operation for user space HFLIP and VFLIP v4l2
* controls will need to be internally inverted.
*
* Rotation also changes the bayer pattern.
*/
if (sensor->hwcfg.module_board_orient ==
CCS_MODULE_BOARD_ORIENT_180)
sensor->hvflip_inv_mask =
CCS_IMAGE_ORIENTATION_HORIZONTAL_MIRROR |
CCS_IMAGE_ORIENTATION_VERTICAL_FLIP;
rval = ccs_call_quirk(sensor, limits);
if (rval) {
dev_err(&client->dev, "limits quirks failed\n");
goto out_free_ccs_limits;
}
if (CCS_LIM(sensor, BINNING_CAPABILITY)) {
sensor->nbinning_subtypes =
min_t(u8, CCS_LIM(sensor, BINNING_SUB_TYPES),
CCS_LIM_BINNING_SUB_TYPE_MAX_N);
for (i = 0; i < sensor->nbinning_subtypes; i++) {
sensor->binning_subtypes[i].horizontal =
CCS_LIM_AT(sensor, BINNING_SUB_TYPE, i) >>
CCS_BINNING_SUB_TYPE_COLUMN_SHIFT;
sensor->binning_subtypes[i].vertical =
CCS_LIM_AT(sensor, BINNING_SUB_TYPE, i) &
CCS_BINNING_SUB_TYPE_ROW_MASK;
dev_dbg(&client->dev, "binning %xx%x\n",
sensor->binning_subtypes[i].horizontal,
sensor->binning_subtypes[i].vertical);
}
}
sensor->binning_horizontal = 1;
sensor->binning_vertical = 1;
if (device_create_file(&client->dev, &dev_attr_ident) != 0) {
dev_err(&client->dev, "sysfs ident entry creation failed\n");
rval = -ENOENT;
goto out_free_ccs_limits;
}
if (sensor->minfo.smiapp_version &&
CCS_LIM(sensor, DATA_TRANSFER_IF_CAPABILITY) &
CCS_DATA_TRANSFER_IF_CAPABILITY_SUPPORTED) {
if (device_create_file(&client->dev, &dev_attr_nvm) != 0) {
dev_err(&client->dev, "sysfs nvm entry failed\n");
rval = -EBUSY;
goto out_cleanup;
}
}
if (!CCS_LIM(sensor, MIN_OP_SYS_CLK_DIV) ||
!CCS_LIM(sensor, MAX_OP_SYS_CLK_DIV) ||
!CCS_LIM(sensor, MIN_OP_PIX_CLK_DIV) ||
!CCS_LIM(sensor, MAX_OP_PIX_CLK_DIV)) {
/* No OP clock branch */
sensor->pll.flags |= CCS_PLL_FLAG_NO_OP_CLOCKS;
} else if (CCS_LIM(sensor, SCALING_CAPABILITY)
!= CCS_SCALING_CAPABILITY_NONE ||
CCS_LIM(sensor, DIGITAL_CROP_CAPABILITY)
== CCS_DIGITAL_CROP_CAPABILITY_INPUT_CROP) {
/* We have a scaler or digital crop. */
sensor->scaler = &sensor->ssds[sensor->ssds_used];
sensor->ssds_used++;
}
sensor->binner = &sensor->ssds[sensor->ssds_used];
sensor->ssds_used++;
sensor->pixel_array = &sensor->ssds[sensor->ssds_used];
sensor->ssds_used++;
sensor->scale_m = CCS_LIM(sensor, SCALER_N_MIN);
/* prepare PLL configuration input values */
sensor->pll.bus_type = CCS_PLL_BUS_TYPE_CSI2_DPHY;
sensor->pll.csi2.lanes = sensor->hwcfg.lanes;
if (CCS_LIM(sensor, CLOCK_CALCULATION) &
CCS_CLOCK_CALCULATION_LANE_SPEED) {
sensor->pll.flags |= CCS_PLL_FLAG_LANE_SPEED_MODEL;
if (CCS_LIM(sensor, CLOCK_CALCULATION) &
CCS_CLOCK_CALCULATION_LINK_DECOUPLED) {
sensor->pll.vt_lanes =
CCS_LIM(sensor, NUM_OF_VT_LANES) + 1;
sensor->pll.op_lanes =
CCS_LIM(sensor, NUM_OF_OP_LANES) + 1;
sensor->pll.flags |= CCS_PLL_FLAG_LINK_DECOUPLED;
} else {
sensor->pll.vt_lanes = sensor->pll.csi2.lanes;
sensor->pll.op_lanes = sensor->pll.csi2.lanes;
}
}
if (CCS_LIM(sensor, CLOCK_TREE_PLL_CAPABILITY) &
CCS_CLOCK_TREE_PLL_CAPABILITY_EXT_DIVIDER)
sensor->pll.flags |= CCS_PLL_FLAG_EXT_IP_PLL_DIVIDER;
if (CCS_LIM(sensor, CLOCK_TREE_PLL_CAPABILITY) &
CCS_CLOCK_TREE_PLL_CAPABILITY_FLEXIBLE_OP_PIX_CLK_DIV)
sensor->pll.flags |= CCS_PLL_FLAG_FLEXIBLE_OP_PIX_CLK_DIV;
if (CCS_LIM(sensor, FIFO_SUPPORT_CAPABILITY) &
CCS_FIFO_SUPPORT_CAPABILITY_DERATING)
sensor->pll.flags |= CCS_PLL_FLAG_FIFO_DERATING;
if (CCS_LIM(sensor, FIFO_SUPPORT_CAPABILITY) &
CCS_FIFO_SUPPORT_CAPABILITY_DERATING_OVERRATING)
sensor->pll.flags |= CCS_PLL_FLAG_FIFO_DERATING |
CCS_PLL_FLAG_FIFO_OVERRATING;
if (CCS_LIM(sensor, CLOCK_TREE_PLL_CAPABILITY) &
CCS_CLOCK_TREE_PLL_CAPABILITY_DUAL_PLL) {
if (CCS_LIM(sensor, CLOCK_TREE_PLL_CAPABILITY) &
CCS_CLOCK_TREE_PLL_CAPABILITY_SINGLE_PLL) {
u32 v;
/* Use sensor default in PLL mode selection */
rval = ccs_read(sensor, PLL_MODE, &v);
if (rval)
goto out_cleanup;
if (v == CCS_PLL_MODE_DUAL)
sensor->pll.flags |= CCS_PLL_FLAG_DUAL_PLL;
} else {
sensor->pll.flags |= CCS_PLL_FLAG_DUAL_PLL;
}
if (CCS_LIM(sensor, CLOCK_CALCULATION) &
CCS_CLOCK_CALCULATION_DUAL_PLL_OP_SYS_DDR)
sensor->pll.flags |= CCS_PLL_FLAG_OP_SYS_DDR;
if (CCS_LIM(sensor, CLOCK_CALCULATION) &
CCS_CLOCK_CALCULATION_DUAL_PLL_OP_PIX_DDR)
sensor->pll.flags |= CCS_PLL_FLAG_OP_PIX_DDR;
}
sensor->pll.op_bits_per_lane = CCS_LIM(sensor, OP_BITS_PER_LANE);
sensor->pll.ext_clk_freq_hz = sensor->hwcfg.ext_clk;
sensor->pll.scale_n = CCS_LIM(sensor, SCALER_N_MIN);
ccs_create_subdev(sensor, sensor->scaler, " scaler", 2,
MEDIA_ENT_F_PROC_VIDEO_SCALER);
ccs_create_subdev(sensor, sensor->binner, " binner", 2,
MEDIA_ENT_F_PROC_VIDEO_SCALER);
ccs_create_subdev(sensor, sensor->pixel_array, " pixel_array", 1,
MEDIA_ENT_F_CAM_SENSOR);
rval = ccs_init_controls(sensor);
if (rval < 0)
goto out_cleanup;
rval = ccs_call_quirk(sensor, init);
if (rval)
goto out_cleanup;
rval = ccs_get_mbus_formats(sensor);
if (rval) {
rval = -ENODEV;
goto out_cleanup;
}
rval = ccs_init_late_controls(sensor);
if (rval) {
rval = -ENODEV;
goto out_cleanup;
}
mutex_lock(&sensor->mutex);
rval = ccs_pll_blanking_update(sensor);
mutex_unlock(&sensor->mutex);
if (rval) {
dev_err(&client->dev, "update mode failed\n");
goto out_cleanup;
}
sensor->streaming = false;
sensor->dev_init_done = true;
rval = media_entity_pads_init(&sensor->src->sd.entity, 2,
sensor->src->pads);
if (rval < 0)
goto out_media_entity_cleanup;
rval = ccs_write_msr_regs(sensor);
if (rval)
goto out_media_entity_cleanup;
pm_runtime_set_active(&client->dev);
pm_runtime_get_noresume(&client->dev);
pm_runtime_enable(&client->dev);
rval = v4l2_async_register_subdev_sensor(&sensor->src->sd);
if (rval < 0)
goto out_disable_runtime_pm;
pm_runtime_set_autosuspend_delay(&client->dev, 1000);
pm_runtime_use_autosuspend(&client->dev);
pm_runtime_put_autosuspend(&client->dev);
return 0;
out_disable_runtime_pm:
pm_runtime_put_noidle(&client->dev);
pm_runtime_disable(&client->dev);
out_media_entity_cleanup:
media_entity_cleanup(&sensor->src->sd.entity);
out_cleanup:
ccs_cleanup(sensor);
out_release_mdata:
kvfree(sensor->mdata.backing);
out_release_sdata:
kvfree(sensor->sdata.backing);
out_free_ccs_limits:
kfree(sensor->ccs_limits);
out_power_off:
ccs_power_off(&client->dev);
mutex_destroy(&sensor->mutex);
return rval;
}