772 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * Copyright 2020-2022 Bootlin4 * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com>5 */6 7#include <linux/clk.h>8#include <linux/module.h>9#include <linux/of.h>10#include <linux/phy/phy.h>11#include <linux/platform_device.h>12#include <linux/pm_runtime.h>13#include <linux/regmap.h>14#include <linux/reset.h>15#include <media/mipi-csi2.h>16#include <media/v4l2-ctrls.h>17#include <media/v4l2-device.h>18#include <media/v4l2-fwnode.h>19 20#include "sun6i_mipi_csi2.h"21#include "sun6i_mipi_csi2_reg.h"22 23/* Format */24 25static const struct sun6i_mipi_csi2_format sun6i_mipi_csi2_formats[] = {26 {27 .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8,28 .data_type = MIPI_CSI2_DT_RAW8,29 .bpp = 8,30 },31 {32 .mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8,33 .data_type = MIPI_CSI2_DT_RAW8,34 .bpp = 8,35 },36 {37 .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8,38 .data_type = MIPI_CSI2_DT_RAW8,39 .bpp = 8,40 },41 {42 .mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8,43 .data_type = MIPI_CSI2_DT_RAW8,44 .bpp = 8,45 },46 {47 .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10,48 .data_type = MIPI_CSI2_DT_RAW10,49 .bpp = 10,50 },51 {52 .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10,53 .data_type = MIPI_CSI2_DT_RAW10,54 .bpp = 10,55 },56 {57 .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10,58 .data_type = MIPI_CSI2_DT_RAW10,59 .bpp = 10,60 },61 {62 .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10,63 .data_type = MIPI_CSI2_DT_RAW10,64 .bpp = 10,65 },66};67 68static const struct sun6i_mipi_csi2_format *69sun6i_mipi_csi2_format_find(u32 mbus_code)70{71 unsigned int i;72 73 for (i = 0; i < ARRAY_SIZE(sun6i_mipi_csi2_formats); i++)74 if (sun6i_mipi_csi2_formats[i].mbus_code == mbus_code)75 return &sun6i_mipi_csi2_formats[i];76 77 return NULL;78}79 80/* Controller */81 82static void sun6i_mipi_csi2_enable(struct sun6i_mipi_csi2_device *csi2_dev)83{84 struct regmap *regmap = csi2_dev->regmap;85 86 regmap_update_bits(regmap, SUN6I_MIPI_CSI2_CTL_REG,87 SUN6I_MIPI_CSI2_CTL_EN, SUN6I_MIPI_CSI2_CTL_EN);88}89 90static void sun6i_mipi_csi2_disable(struct sun6i_mipi_csi2_device *csi2_dev)91{92 struct regmap *regmap = csi2_dev->regmap;93 94 regmap_update_bits(regmap, SUN6I_MIPI_CSI2_CTL_REG,95 SUN6I_MIPI_CSI2_CTL_EN, 0);96}97 98static void sun6i_mipi_csi2_configure(struct sun6i_mipi_csi2_device *csi2_dev)99{100 struct regmap *regmap = csi2_dev->regmap;101 unsigned int lanes_count =102 csi2_dev->bridge.endpoint.bus.mipi_csi2.num_data_lanes;103 struct v4l2_mbus_framefmt *mbus_format = &csi2_dev->bridge.mbus_format;104 const struct sun6i_mipi_csi2_format *format;105 struct device *dev = csi2_dev->dev;106 u32 version = 0;107 108 format = sun6i_mipi_csi2_format_find(mbus_format->code);109 if (WARN_ON(!format))110 return;111 112 /*113 * The enable flow in the Allwinner BSP is a bit different: the enable114 * and reset bits are set together before starting the CSI controller.115 *116 * In mainline we enable the CSI controller first (due to subdev logic).117 * One reliable way to make this work is to deassert reset, configure118 * registers and enable the controller when everything's ready.119 *120 * However, setting the version enable bit and removing it afterwards121 * appears necessary for capture to work reliably, while replacing it122 * with a delay doesn't do the trick.123 */124 regmap_write(regmap, SUN6I_MIPI_CSI2_CTL_REG,125 SUN6I_MIPI_CSI2_CTL_RESET_N |126 SUN6I_MIPI_CSI2_CTL_VERSION_EN |127 SUN6I_MIPI_CSI2_CTL_UNPK_EN);128 129 regmap_read(regmap, SUN6I_MIPI_CSI2_VERSION_REG, &version);130 131 regmap_update_bits(regmap, SUN6I_MIPI_CSI2_CTL_REG,132 SUN6I_MIPI_CSI2_CTL_VERSION_EN, 0);133 134 dev_dbg(dev, "A31 MIPI CSI-2 version: %04x\n", version);135 136 regmap_write(regmap, SUN6I_MIPI_CSI2_CFG_REG,137 SUN6I_MIPI_CSI2_CFG_CHANNEL_MODE(1) |138 SUN6I_MIPI_CSI2_CFG_LANE_COUNT(lanes_count));139 140 /*141 * Only a single virtual channel (index 0) is currently supported.142 * While the registers do mention multiple physical channels being143 * available (which can be configured to match a specific virtual144 * channel or data type), it's unclear whether channels > 0 are actually145 * connected and available and the reference source code only makes use146 * of channel 0.147 *148 * Using extra channels would also require matching channels to be149 * available on the CSI (and ISP) side, which is also unsure although150 * some CSI implementations are said to support multiple channels for151 * BT656 time-sharing.152 *153 * We still configure virtual channel numbers to ensure that virtual154 * channel 0 only goes to channel 0.155 */156 157 regmap_write(regmap, SUN6I_MIPI_CSI2_VCDT_RX_REG,158 SUN6I_MIPI_CSI2_VCDT_RX_CH_VC(3, 3) |159 SUN6I_MIPI_CSI2_VCDT_RX_CH_VC(2, 2) |160 SUN6I_MIPI_CSI2_VCDT_RX_CH_VC(1, 1) |161 SUN6I_MIPI_CSI2_VCDT_RX_CH_VC(0, 0) |162 SUN6I_MIPI_CSI2_VCDT_RX_CH_DT(0, format->data_type));163 164 regmap_write(regmap, SUN6I_MIPI_CSI2_CH_INT_PD_REG,165 SUN6I_MIPI_CSI2_CH_INT_PD_CLEAR);166}167 168/* V4L2 Subdev */169 170static int sun6i_mipi_csi2_s_stream(struct v4l2_subdev *subdev, int on)171{172 struct sun6i_mipi_csi2_device *csi2_dev = v4l2_get_subdevdata(subdev);173 struct v4l2_subdev *source_subdev = csi2_dev->bridge.source_subdev;174 union phy_configure_opts dphy_opts = { 0 };175 struct phy_configure_opts_mipi_dphy *dphy_cfg = &dphy_opts.mipi_dphy;176 struct v4l2_mbus_framefmt *mbus_format = &csi2_dev->bridge.mbus_format;177 const struct sun6i_mipi_csi2_format *format;178 struct phy *dphy = csi2_dev->dphy;179 struct device *dev = csi2_dev->dev;180 struct v4l2_ctrl *ctrl;181 unsigned int lanes_count =182 csi2_dev->bridge.endpoint.bus.mipi_csi2.num_data_lanes;183 unsigned long pixel_rate;184 int ret;185 186 if (!source_subdev)187 return -ENODEV;188 189 if (!on) {190 v4l2_subdev_call(source_subdev, video, s_stream, 0);191 ret = 0;192 goto disable;193 }194 195 /* Runtime PM */196 197 ret = pm_runtime_resume_and_get(dev);198 if (ret < 0)199 return ret;200 201 /* Sensor Pixel Rate */202 203 ctrl = v4l2_ctrl_find(source_subdev->ctrl_handler, V4L2_CID_PIXEL_RATE);204 if (!ctrl) {205 dev_err(dev, "missing sensor pixel rate\n");206 ret = -ENODEV;207 goto error_pm;208 }209 210 pixel_rate = (unsigned long)v4l2_ctrl_g_ctrl_int64(ctrl);211 if (!pixel_rate) {212 dev_err(dev, "missing (zero) sensor pixel rate\n");213 ret = -ENODEV;214 goto error_pm;215 }216 217 /* D-PHY */218 219 if (!lanes_count) {220 dev_err(dev, "missing (zero) MIPI CSI-2 lanes count\n");221 ret = -ENODEV;222 goto error_pm;223 }224 225 format = sun6i_mipi_csi2_format_find(mbus_format->code);226 if (WARN_ON(!format)) {227 ret = -ENODEV;228 goto error_pm;229 }230 231 phy_mipi_dphy_get_default_config(pixel_rate, format->bpp, lanes_count,232 dphy_cfg);233 234 /*235 * Note that our hardware is using DDR, which is not taken in account by236 * phy_mipi_dphy_get_default_config when calculating hs_clk_rate from237 * the pixel rate, lanes count and bpp.238 *239 * The resulting clock rate is basically the symbol rate over the whole240 * link. The actual clock rate is calculated with division by two since241 * DDR samples both on rising and falling edges.242 */243 244 dev_dbg(dev, "A31 MIPI CSI-2 config:\n");245 dev_dbg(dev, "%ld pixels/s, %u bits/pixel, %u lanes, %lu Hz clock\n",246 pixel_rate, format->bpp, lanes_count,247 dphy_cfg->hs_clk_rate / 2);248 249 ret = phy_reset(dphy);250 if (ret) {251 dev_err(dev, "failed to reset MIPI D-PHY\n");252 goto error_pm;253 }254 255 ret = phy_configure(dphy, &dphy_opts);256 if (ret) {257 dev_err(dev, "failed to configure MIPI D-PHY\n");258 goto error_pm;259 }260 261 /* Controller */262 263 sun6i_mipi_csi2_configure(csi2_dev);264 sun6i_mipi_csi2_enable(csi2_dev);265 266 /* D-PHY */267 268 ret = phy_power_on(dphy);269 if (ret) {270 dev_err(dev, "failed to power on MIPI D-PHY\n");271 goto error_pm;272 }273 274 /* Source */275 276 ret = v4l2_subdev_call(source_subdev, video, s_stream, 1);277 if (ret && ret != -ENOIOCTLCMD)278 goto disable;279 280 return 0;281 282disable:283 phy_power_off(dphy);284 sun6i_mipi_csi2_disable(csi2_dev);285 286error_pm:287 pm_runtime_put(dev);288 289 return ret;290}291 292static const struct v4l2_subdev_video_ops sun6i_mipi_csi2_video_ops = {293 .s_stream = sun6i_mipi_csi2_s_stream,294};295 296static void297sun6i_mipi_csi2_mbus_format_prepare(struct v4l2_mbus_framefmt *mbus_format)298{299 if (!sun6i_mipi_csi2_format_find(mbus_format->code))300 mbus_format->code = sun6i_mipi_csi2_formats[0].mbus_code;301 302 mbus_format->field = V4L2_FIELD_NONE;303 mbus_format->colorspace = V4L2_COLORSPACE_RAW;304 mbus_format->quantization = V4L2_QUANTIZATION_DEFAULT;305 mbus_format->xfer_func = V4L2_XFER_FUNC_DEFAULT;306}307 308static int sun6i_mipi_csi2_init_state(struct v4l2_subdev *subdev,309 struct v4l2_subdev_state *state)310{311 struct sun6i_mipi_csi2_device *csi2_dev = v4l2_get_subdevdata(subdev);312 unsigned int pad = SUN6I_MIPI_CSI2_PAD_SINK;313 struct v4l2_mbus_framefmt *mbus_format =314 v4l2_subdev_state_get_format(state, pad);315 struct mutex *lock = &csi2_dev->bridge.lock;316 317 mutex_lock(lock);318 319 mbus_format->code = sun6i_mipi_csi2_formats[0].mbus_code;320 mbus_format->width = 640;321 mbus_format->height = 480;322 323 sun6i_mipi_csi2_mbus_format_prepare(mbus_format);324 325 mutex_unlock(lock);326 327 return 0;328}329 330static int331sun6i_mipi_csi2_enum_mbus_code(struct v4l2_subdev *subdev,332 struct v4l2_subdev_state *state,333 struct v4l2_subdev_mbus_code_enum *code_enum)334{335 if (code_enum->index >= ARRAY_SIZE(sun6i_mipi_csi2_formats))336 return -EINVAL;337 338 code_enum->code = sun6i_mipi_csi2_formats[code_enum->index].mbus_code;339 340 return 0;341}342 343static int sun6i_mipi_csi2_get_fmt(struct v4l2_subdev *subdev,344 struct v4l2_subdev_state *state,345 struct v4l2_subdev_format *format)346{347 struct sun6i_mipi_csi2_device *csi2_dev = v4l2_get_subdevdata(subdev);348 struct v4l2_mbus_framefmt *mbus_format = &format->format;349 struct mutex *lock = &csi2_dev->bridge.lock;350 351 mutex_lock(lock);352 353 if (format->which == V4L2_SUBDEV_FORMAT_TRY)354 *mbus_format = *v4l2_subdev_state_get_format(state,355 format->pad);356 else357 *mbus_format = csi2_dev->bridge.mbus_format;358 359 mutex_unlock(lock);360 361 return 0;362}363 364static int sun6i_mipi_csi2_set_fmt(struct v4l2_subdev *subdev,365 struct v4l2_subdev_state *state,366 struct v4l2_subdev_format *format)367{368 struct sun6i_mipi_csi2_device *csi2_dev = v4l2_get_subdevdata(subdev);369 struct v4l2_mbus_framefmt *mbus_format = &format->format;370 struct mutex *lock = &csi2_dev->bridge.lock;371 372 mutex_lock(lock);373 374 sun6i_mipi_csi2_mbus_format_prepare(mbus_format);375 376 if (format->which == V4L2_SUBDEV_FORMAT_TRY)377 *v4l2_subdev_state_get_format(state, format->pad) =378 *mbus_format;379 else380 csi2_dev->bridge.mbus_format = *mbus_format;381 382 mutex_unlock(lock);383 384 return 0;385}386 387static const struct v4l2_subdev_pad_ops sun6i_mipi_csi2_pad_ops = {388 .enum_mbus_code = sun6i_mipi_csi2_enum_mbus_code,389 .get_fmt = sun6i_mipi_csi2_get_fmt,390 .set_fmt = sun6i_mipi_csi2_set_fmt,391};392 393static const struct v4l2_subdev_ops sun6i_mipi_csi2_subdev_ops = {394 .video = &sun6i_mipi_csi2_video_ops,395 .pad = &sun6i_mipi_csi2_pad_ops,396};397 398static const struct v4l2_subdev_internal_ops sun6i_mipi_csi2_internal_ops = {399 .init_state = sun6i_mipi_csi2_init_state,400};401 402/* Media Entity */403 404static const struct media_entity_operations sun6i_mipi_csi2_entity_ops = {405 .link_validate = v4l2_subdev_link_validate,406};407 408/* V4L2 Async */409 410static int411sun6i_mipi_csi2_notifier_bound(struct v4l2_async_notifier *notifier,412 struct v4l2_subdev *remote_subdev,413 struct v4l2_async_connection *async_subdev)414{415 struct v4l2_subdev *subdev = notifier->sd;416 struct sun6i_mipi_csi2_device *csi2_dev =417 container_of(notifier, struct sun6i_mipi_csi2_device,418 bridge.notifier);419 struct media_entity *sink_entity = &subdev->entity;420 struct media_entity *source_entity = &remote_subdev->entity;421 struct device *dev = csi2_dev->dev;422 int sink_pad_index = 0;423 int source_pad_index;424 int ret;425 426 ret = media_entity_get_fwnode_pad(source_entity, remote_subdev->fwnode,427 MEDIA_PAD_FL_SOURCE);428 if (ret < 0) {429 dev_err(dev, "missing source pad in external entity %s\n",430 source_entity->name);431 return -EINVAL;432 }433 434 source_pad_index = ret;435 436 dev_dbg(dev, "creating %s:%u -> %s:%u link\n", source_entity->name,437 source_pad_index, sink_entity->name, sink_pad_index);438 439 ret = media_create_pad_link(source_entity, source_pad_index,440 sink_entity, sink_pad_index,441 MEDIA_LNK_FL_ENABLED |442 MEDIA_LNK_FL_IMMUTABLE);443 if (ret) {444 dev_err(dev, "failed to create %s:%u -> %s:%u link\n",445 source_entity->name, source_pad_index,446 sink_entity->name, sink_pad_index);447 return ret;448 }449 450 csi2_dev->bridge.source_subdev = remote_subdev;451 452 return 0;453}454 455static const struct v4l2_async_notifier_operations456sun6i_mipi_csi2_notifier_ops = {457 .bound = sun6i_mipi_csi2_notifier_bound,458};459 460/* Bridge */461 462static int463sun6i_mipi_csi2_bridge_source_setup(struct sun6i_mipi_csi2_device *csi2_dev)464{465 struct v4l2_async_notifier *notifier = &csi2_dev->bridge.notifier;466 struct v4l2_fwnode_endpoint *endpoint = &csi2_dev->bridge.endpoint;467 struct v4l2_async_connection *subdev_async;468 struct fwnode_handle *handle;469 struct device *dev = csi2_dev->dev;470 int ret;471 472 handle = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0,473 FWNODE_GRAPH_ENDPOINT_NEXT);474 if (!handle)475 return -ENODEV;476 477 endpoint->bus_type = V4L2_MBUS_CSI2_DPHY;478 479 ret = v4l2_fwnode_endpoint_parse(handle, endpoint);480 if (ret)481 goto complete;482 483 subdev_async =484 v4l2_async_nf_add_fwnode_remote(notifier, handle,485 struct v4l2_async_connection);486 if (IS_ERR(subdev_async))487 ret = PTR_ERR(subdev_async);488 489complete:490 fwnode_handle_put(handle);491 492 return ret;493}494 495static int sun6i_mipi_csi2_bridge_setup(struct sun6i_mipi_csi2_device *csi2_dev)496{497 struct sun6i_mipi_csi2_bridge *bridge = &csi2_dev->bridge;498 struct v4l2_subdev *subdev = &bridge->subdev;499 struct v4l2_async_notifier *notifier = &bridge->notifier;500 struct media_pad *pads = bridge->pads;501 struct device *dev = csi2_dev->dev;502 bool notifier_registered = false;503 int ret;504 505 mutex_init(&bridge->lock);506 507 /* V4L2 Subdev */508 509 v4l2_subdev_init(subdev, &sun6i_mipi_csi2_subdev_ops);510 subdev->internal_ops = &sun6i_mipi_csi2_internal_ops;511 strscpy(subdev->name, SUN6I_MIPI_CSI2_NAME, sizeof(subdev->name));512 subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;513 subdev->owner = THIS_MODULE;514 subdev->dev = dev;515 516 v4l2_set_subdevdata(subdev, csi2_dev);517 518 /* Media Entity */519 520 subdev->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;521 subdev->entity.ops = &sun6i_mipi_csi2_entity_ops;522 523 /* Media Pads */524 525 pads[SUN6I_MIPI_CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK |526 MEDIA_PAD_FL_MUST_CONNECT;527 pads[SUN6I_MIPI_CSI2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE |528 MEDIA_PAD_FL_MUST_CONNECT;529 530 ret = media_entity_pads_init(&subdev->entity, SUN6I_MIPI_CSI2_PAD_COUNT,531 pads);532 if (ret)533 return ret;534 535 /* V4L2 Async */536 537 v4l2_async_subdev_nf_init(notifier, subdev);538 notifier->ops = &sun6i_mipi_csi2_notifier_ops;539 540 ret = sun6i_mipi_csi2_bridge_source_setup(csi2_dev);541 if (ret && ret != -ENODEV)542 goto error_v4l2_notifier_cleanup;543 544 /* Only register the notifier when a sensor is connected. */545 if (ret != -ENODEV) {546 ret = v4l2_async_nf_register(notifier);547 if (ret < 0)548 goto error_v4l2_notifier_cleanup;549 550 notifier_registered = true;551 }552 553 /* V4L2 Subdev */554 555 ret = v4l2_async_register_subdev(subdev);556 if (ret < 0)557 goto error_v4l2_notifier_unregister;558 559 return 0;560 561error_v4l2_notifier_unregister:562 if (notifier_registered)563 v4l2_async_nf_unregister(notifier);564 565error_v4l2_notifier_cleanup:566 v4l2_async_nf_cleanup(notifier);567 568 media_entity_cleanup(&subdev->entity);569 570 return ret;571}572 573static void574sun6i_mipi_csi2_bridge_cleanup(struct sun6i_mipi_csi2_device *csi2_dev)575{576 struct v4l2_subdev *subdev = &csi2_dev->bridge.subdev;577 struct v4l2_async_notifier *notifier = &csi2_dev->bridge.notifier;578 579 v4l2_async_unregister_subdev(subdev);580 v4l2_async_nf_unregister(notifier);581 v4l2_async_nf_cleanup(notifier);582 media_entity_cleanup(&subdev->entity);583}584 585/* Platform */586 587static int sun6i_mipi_csi2_suspend(struct device *dev)588{589 struct sun6i_mipi_csi2_device *csi2_dev = dev_get_drvdata(dev);590 591 clk_disable_unprepare(csi2_dev->clock_mod);592 reset_control_assert(csi2_dev->reset);593 594 return 0;595}596 597static int sun6i_mipi_csi2_resume(struct device *dev)598{599 struct sun6i_mipi_csi2_device *csi2_dev = dev_get_drvdata(dev);600 int ret;601 602 ret = reset_control_deassert(csi2_dev->reset);603 if (ret) {604 dev_err(dev, "failed to deassert reset\n");605 return ret;606 }607 608 ret = clk_prepare_enable(csi2_dev->clock_mod);609 if (ret) {610 dev_err(dev, "failed to enable module clock\n");611 goto error_reset;612 }613 614 return 0;615 616error_reset:617 reset_control_assert(csi2_dev->reset);618 619 return ret;620}621 622static const struct dev_pm_ops sun6i_mipi_csi2_pm_ops = {623 .runtime_suspend = sun6i_mipi_csi2_suspend,624 .runtime_resume = sun6i_mipi_csi2_resume,625};626 627static const struct regmap_config sun6i_mipi_csi2_regmap_config = {628 .reg_bits = 32,629 .reg_stride = 4,630 .val_bits = 32,631 .max_register = 0x400,632};633 634static int635sun6i_mipi_csi2_resources_setup(struct sun6i_mipi_csi2_device *csi2_dev,636 struct platform_device *platform_dev)637{638 struct device *dev = csi2_dev->dev;639 void __iomem *io_base;640 int ret;641 642 /* Registers */643 644 io_base = devm_platform_ioremap_resource(platform_dev, 0);645 if (IS_ERR(io_base))646 return PTR_ERR(io_base);647 648 csi2_dev->regmap =649 devm_regmap_init_mmio_clk(dev, "bus", io_base,650 &sun6i_mipi_csi2_regmap_config);651 if (IS_ERR(csi2_dev->regmap)) {652 dev_err(dev, "failed to init register map\n");653 return PTR_ERR(csi2_dev->regmap);654 }655 656 /* Clock */657 658 csi2_dev->clock_mod = devm_clk_get(dev, "mod");659 if (IS_ERR(csi2_dev->clock_mod)) {660 dev_err(dev, "failed to acquire mod clock\n");661 return PTR_ERR(csi2_dev->clock_mod);662 }663 664 ret = clk_set_rate_exclusive(csi2_dev->clock_mod, 297000000);665 if (ret) {666 dev_err(dev, "failed to set mod clock rate\n");667 return ret;668 }669 670 /* Reset */671 672 csi2_dev->reset = devm_reset_control_get_shared(dev, NULL);673 if (IS_ERR(csi2_dev->reset)) {674 dev_err(dev, "failed to get reset controller\n");675 ret = PTR_ERR(csi2_dev->reset);676 goto error_clock_rate_exclusive;677 }678 679 /* D-PHY */680 681 csi2_dev->dphy = devm_phy_get(dev, "dphy");682 if (IS_ERR(csi2_dev->dphy)) {683 dev_err(dev, "failed to get MIPI D-PHY\n");684 ret = PTR_ERR(csi2_dev->dphy);685 goto error_clock_rate_exclusive;686 }687 688 ret = phy_init(csi2_dev->dphy);689 if (ret) {690 dev_err(dev, "failed to initialize MIPI D-PHY\n");691 goto error_clock_rate_exclusive;692 }693 694 /* Runtime PM */695 696 pm_runtime_enable(dev);697 698 return 0;699 700error_clock_rate_exclusive:701 clk_rate_exclusive_put(csi2_dev->clock_mod);702 703 return ret;704}705 706static void707sun6i_mipi_csi2_resources_cleanup(struct sun6i_mipi_csi2_device *csi2_dev)708{709 pm_runtime_disable(csi2_dev->dev);710 phy_exit(csi2_dev->dphy);711 clk_rate_exclusive_put(csi2_dev->clock_mod);712}713 714static int sun6i_mipi_csi2_probe(struct platform_device *platform_dev)715{716 struct sun6i_mipi_csi2_device *csi2_dev;717 struct device *dev = &platform_dev->dev;718 int ret;719 720 csi2_dev = devm_kzalloc(dev, sizeof(*csi2_dev), GFP_KERNEL);721 if (!csi2_dev)722 return -ENOMEM;723 724 csi2_dev->dev = dev;725 platform_set_drvdata(platform_dev, csi2_dev);726 727 ret = sun6i_mipi_csi2_resources_setup(csi2_dev, platform_dev);728 if (ret)729 return ret;730 731 ret = sun6i_mipi_csi2_bridge_setup(csi2_dev);732 if (ret)733 goto error_resources;734 735 return 0;736 737error_resources:738 sun6i_mipi_csi2_resources_cleanup(csi2_dev);739 740 return ret;741}742 743static void sun6i_mipi_csi2_remove(struct platform_device *platform_dev)744{745 struct sun6i_mipi_csi2_device *csi2_dev =746 platform_get_drvdata(platform_dev);747 748 sun6i_mipi_csi2_bridge_cleanup(csi2_dev);749 sun6i_mipi_csi2_resources_cleanup(csi2_dev);750}751 752static const struct of_device_id sun6i_mipi_csi2_of_match[] = {753 { .compatible = "allwinner,sun6i-a31-mipi-csi2" },754 {},755};756MODULE_DEVICE_TABLE(of, sun6i_mipi_csi2_of_match);757 758static struct platform_driver sun6i_mipi_csi2_platform_driver = {759 .probe = sun6i_mipi_csi2_probe,760 .remove_new = sun6i_mipi_csi2_remove,761 .driver = {762 .name = SUN6I_MIPI_CSI2_NAME,763 .of_match_table = sun6i_mipi_csi2_of_match,764 .pm = &sun6i_mipi_csi2_pm_ops,765 },766};767module_platform_driver(sun6i_mipi_csi2_platform_driver);768 769MODULE_DESCRIPTION("Allwinner A31 MIPI CSI-2 Controller Driver");770MODULE_AUTHOR("Paul Kocialkowski <paul.kocialkowski@bootlin.com>");771MODULE_LICENSE("GPL");772