162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Support for the camera device found on Marvell MMP processors; known 462306a36Sopenharmony_ci * to work with the Armada 610 as used in the OLPC 1.75 system. 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * Copyright 2011 Jonathan Corbet <corbet@lwn.net> 762306a36Sopenharmony_ci * Copyright 2018 Lubomir Rintel <lkundrak@v3.sk> 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/init.h> 1162306a36Sopenharmony_ci#include <linux/kernel.h> 1262306a36Sopenharmony_ci#include <linux/module.h> 1362306a36Sopenharmony_ci#include <linux/interrupt.h> 1462306a36Sopenharmony_ci#include <linux/spinlock.h> 1562306a36Sopenharmony_ci#include <linux/slab.h> 1662306a36Sopenharmony_ci#include <linux/videodev2.h> 1762306a36Sopenharmony_ci#include <media/v4l2-device.h> 1862306a36Sopenharmony_ci#include <linux/platform_data/media/mmp-camera.h> 1962306a36Sopenharmony_ci#include <linux/device.h> 2062306a36Sopenharmony_ci#include <linux/of.h> 2162306a36Sopenharmony_ci#include <linux/of_platform.h> 2262306a36Sopenharmony_ci#include <linux/platform_device.h> 2362306a36Sopenharmony_ci#include <linux/pm_runtime.h> 2462306a36Sopenharmony_ci#include <linux/io.h> 2562306a36Sopenharmony_ci#include <linux/list.h> 2662306a36Sopenharmony_ci#include <linux/pm.h> 2762306a36Sopenharmony_ci#include <linux/clk.h> 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci#include "mcam-core.h" 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ciMODULE_ALIAS("platform:mmp-camera"); 3262306a36Sopenharmony_ciMODULE_AUTHOR("Jonathan Corbet <corbet@lwn.net>"); 3362306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_cistatic char *mcam_clks[] = {"axi", "func", "phy"}; 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_cistruct mmp_camera { 3862306a36Sopenharmony_ci struct platform_device *pdev; 3962306a36Sopenharmony_ci struct mcam_camera mcam; 4062306a36Sopenharmony_ci struct list_head devlist; 4162306a36Sopenharmony_ci struct clk *mipi_clk; 4262306a36Sopenharmony_ci int irq; 4362306a36Sopenharmony_ci}; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_cistatic inline struct mmp_camera *mcam_to_cam(struct mcam_camera *mcam) 4662306a36Sopenharmony_ci{ 4762306a36Sopenharmony_ci return container_of(mcam, struct mmp_camera, mcam); 4862306a36Sopenharmony_ci} 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci/* 5162306a36Sopenharmony_ci * calc the dphy register values 5262306a36Sopenharmony_ci * There are three dphy registers being used. 5362306a36Sopenharmony_ci * dphy[0] - CSI2_DPHY3 5462306a36Sopenharmony_ci * dphy[1] - CSI2_DPHY5 5562306a36Sopenharmony_ci * dphy[2] - CSI2_DPHY6 5662306a36Sopenharmony_ci * CSI2_DPHY3 and CSI2_DPHY6 can be set with a default value 5762306a36Sopenharmony_ci * or be calculated dynamically 5862306a36Sopenharmony_ci */ 5962306a36Sopenharmony_cistatic void mmpcam_calc_dphy(struct mcam_camera *mcam) 6062306a36Sopenharmony_ci{ 6162306a36Sopenharmony_ci struct mmp_camera *cam = mcam_to_cam(mcam); 6262306a36Sopenharmony_ci struct mmp_camera_platform_data *pdata = cam->pdev->dev.platform_data; 6362306a36Sopenharmony_ci struct device *dev = &cam->pdev->dev; 6462306a36Sopenharmony_ci unsigned long tx_clk_esc; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci /* 6762306a36Sopenharmony_ci * If CSI2_DPHY3 is calculated dynamically, 6862306a36Sopenharmony_ci * pdata->lane_clk should be already set 6962306a36Sopenharmony_ci * either in the board driver statically 7062306a36Sopenharmony_ci * or in the sensor driver dynamically. 7162306a36Sopenharmony_ci */ 7262306a36Sopenharmony_ci /* 7362306a36Sopenharmony_ci * dphy[0] - CSI2_DPHY3: 7462306a36Sopenharmony_ci * bit 0 ~ bit 7: HS Term Enable. 7562306a36Sopenharmony_ci * defines the time that the DPHY 7662306a36Sopenharmony_ci * wait before enabling the data 7762306a36Sopenharmony_ci * lane termination after detecting 7862306a36Sopenharmony_ci * that the sensor has driven the data 7962306a36Sopenharmony_ci * lanes to the LP00 bridge state. 8062306a36Sopenharmony_ci * The value is calculated by: 8162306a36Sopenharmony_ci * (Max T(D_TERM_EN)/Period(DDR)) - 1 8262306a36Sopenharmony_ci * bit 8 ~ bit 15: HS_SETTLE 8362306a36Sopenharmony_ci * Time interval during which the HS 8462306a36Sopenharmony_ci * receiver shall ignore any Data Lane 8562306a36Sopenharmony_ci * HS transitions. 8662306a36Sopenharmony_ci * The value has been calibrated on 8762306a36Sopenharmony_ci * different boards. It seems to work well. 8862306a36Sopenharmony_ci * 8962306a36Sopenharmony_ci * More detail please refer 9062306a36Sopenharmony_ci * MIPI Alliance Spectification for D-PHY 9162306a36Sopenharmony_ci * document for explanation of HS-SETTLE 9262306a36Sopenharmony_ci * and D-TERM-EN. 9362306a36Sopenharmony_ci */ 9462306a36Sopenharmony_ci switch (pdata->dphy3_algo) { 9562306a36Sopenharmony_ci case DPHY3_ALGO_PXA910: 9662306a36Sopenharmony_ci /* 9762306a36Sopenharmony_ci * Calculate CSI2_DPHY3 algo for PXA910 9862306a36Sopenharmony_ci */ 9962306a36Sopenharmony_ci pdata->dphy[0] = 10062306a36Sopenharmony_ci (((1 + (pdata->lane_clk * 80) / 1000) & 0xff) << 8) 10162306a36Sopenharmony_ci | (1 + pdata->lane_clk * 35 / 1000); 10262306a36Sopenharmony_ci break; 10362306a36Sopenharmony_ci case DPHY3_ALGO_PXA2128: 10462306a36Sopenharmony_ci /* 10562306a36Sopenharmony_ci * Calculate CSI2_DPHY3 algo for PXA2128 10662306a36Sopenharmony_ci */ 10762306a36Sopenharmony_ci pdata->dphy[0] = 10862306a36Sopenharmony_ci (((2 + (pdata->lane_clk * 110) / 1000) & 0xff) << 8) 10962306a36Sopenharmony_ci | (1 + pdata->lane_clk * 35 / 1000); 11062306a36Sopenharmony_ci break; 11162306a36Sopenharmony_ci default: 11262306a36Sopenharmony_ci /* 11362306a36Sopenharmony_ci * Use default CSI2_DPHY3 value for PXA688/PXA988 11462306a36Sopenharmony_ci */ 11562306a36Sopenharmony_ci dev_dbg(dev, "camera: use the default CSI2_DPHY3 value\n"); 11662306a36Sopenharmony_ci } 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci /* 11962306a36Sopenharmony_ci * mipi_clk will never be changed, it is a fixed value on MMP 12062306a36Sopenharmony_ci */ 12162306a36Sopenharmony_ci if (IS_ERR(cam->mipi_clk)) 12262306a36Sopenharmony_ci return; 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci /* get the escape clk, this is hard coded */ 12562306a36Sopenharmony_ci clk_prepare_enable(cam->mipi_clk); 12662306a36Sopenharmony_ci tx_clk_esc = (clk_get_rate(cam->mipi_clk) / 1000000) / 12; 12762306a36Sopenharmony_ci clk_disable_unprepare(cam->mipi_clk); 12862306a36Sopenharmony_ci /* 12962306a36Sopenharmony_ci * dphy[2] - CSI2_DPHY6: 13062306a36Sopenharmony_ci * bit 0 ~ bit 7: CK Term Enable 13162306a36Sopenharmony_ci * Time for the Clock Lane receiver to enable the HS line 13262306a36Sopenharmony_ci * termination. The value is calculated similarly with 13362306a36Sopenharmony_ci * HS Term Enable 13462306a36Sopenharmony_ci * bit 8 ~ bit 15: CK Settle 13562306a36Sopenharmony_ci * Time interval during which the HS receiver shall ignore 13662306a36Sopenharmony_ci * any Clock Lane HS transitions. 13762306a36Sopenharmony_ci * The value is calibrated on the boards. 13862306a36Sopenharmony_ci */ 13962306a36Sopenharmony_ci pdata->dphy[2] = 14062306a36Sopenharmony_ci ((((534 * tx_clk_esc) / 2000 - 1) & 0xff) << 8) 14162306a36Sopenharmony_ci | (((38 * tx_clk_esc) / 1000 - 1) & 0xff); 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci dev_dbg(dev, "camera: DPHY sets: dphy3=0x%x, dphy5=0x%x, dphy6=0x%x\n", 14462306a36Sopenharmony_ci pdata->dphy[0], pdata->dphy[1], pdata->dphy[2]); 14562306a36Sopenharmony_ci} 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_cistatic irqreturn_t mmpcam_irq(int irq, void *data) 14862306a36Sopenharmony_ci{ 14962306a36Sopenharmony_ci struct mcam_camera *mcam = data; 15062306a36Sopenharmony_ci unsigned int irqs, handled; 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci spin_lock(&mcam->dev_lock); 15362306a36Sopenharmony_ci irqs = mcam_reg_read(mcam, REG_IRQSTAT); 15462306a36Sopenharmony_ci handled = mccic_irq(mcam, irqs); 15562306a36Sopenharmony_ci spin_unlock(&mcam->dev_lock); 15662306a36Sopenharmony_ci return IRQ_RETVAL(handled); 15762306a36Sopenharmony_ci} 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_cistatic void mcam_init_clk(struct mcam_camera *mcam) 16062306a36Sopenharmony_ci{ 16162306a36Sopenharmony_ci unsigned int i; 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci for (i = 0; i < NR_MCAM_CLK; i++) { 16462306a36Sopenharmony_ci if (mcam_clks[i] != NULL) { 16562306a36Sopenharmony_ci /* Some clks are not necessary on some boards 16662306a36Sopenharmony_ci * We still try to run even it fails getting clk 16762306a36Sopenharmony_ci */ 16862306a36Sopenharmony_ci mcam->clk[i] = devm_clk_get(mcam->dev, mcam_clks[i]); 16962306a36Sopenharmony_ci if (IS_ERR(mcam->clk[i])) 17062306a36Sopenharmony_ci dev_warn(mcam->dev, "Could not get clk: %s\n", 17162306a36Sopenharmony_ci mcam_clks[i]); 17262306a36Sopenharmony_ci } 17362306a36Sopenharmony_ci } 17462306a36Sopenharmony_ci} 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_cistatic int mmpcam_probe(struct platform_device *pdev) 17762306a36Sopenharmony_ci{ 17862306a36Sopenharmony_ci struct mmp_camera *cam; 17962306a36Sopenharmony_ci struct mcam_camera *mcam; 18062306a36Sopenharmony_ci struct resource *res; 18162306a36Sopenharmony_ci struct fwnode_handle *ep; 18262306a36Sopenharmony_ci struct mmp_camera_platform_data *pdata; 18362306a36Sopenharmony_ci struct v4l2_async_connection *asd; 18462306a36Sopenharmony_ci int ret; 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci cam = devm_kzalloc(&pdev->dev, sizeof(*cam), GFP_KERNEL); 18762306a36Sopenharmony_ci if (cam == NULL) 18862306a36Sopenharmony_ci return -ENOMEM; 18962306a36Sopenharmony_ci platform_set_drvdata(pdev, cam); 19062306a36Sopenharmony_ci cam->pdev = pdev; 19162306a36Sopenharmony_ci INIT_LIST_HEAD(&cam->devlist); 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci mcam = &cam->mcam; 19462306a36Sopenharmony_ci mcam->calc_dphy = mmpcam_calc_dphy; 19562306a36Sopenharmony_ci mcam->dev = &pdev->dev; 19662306a36Sopenharmony_ci pdata = pdev->dev.platform_data; 19762306a36Sopenharmony_ci if (pdata) { 19862306a36Sopenharmony_ci mcam->mclk_src = pdata->mclk_src; 19962306a36Sopenharmony_ci mcam->mclk_div = pdata->mclk_div; 20062306a36Sopenharmony_ci mcam->bus_type = pdata->bus_type; 20162306a36Sopenharmony_ci mcam->dphy = pdata->dphy; 20262306a36Sopenharmony_ci mcam->lane = pdata->lane; 20362306a36Sopenharmony_ci } else { 20462306a36Sopenharmony_ci /* 20562306a36Sopenharmony_ci * These are values that used to be hardcoded in mcam-core and 20662306a36Sopenharmony_ci * work well on a OLPC XO 1.75 with a parallel bus sensor. 20762306a36Sopenharmony_ci * If it turns out other setups make sense, the values should 20862306a36Sopenharmony_ci * be obtained from the device tree. 20962306a36Sopenharmony_ci */ 21062306a36Sopenharmony_ci mcam->mclk_src = 3; 21162306a36Sopenharmony_ci mcam->mclk_div = 2; 21262306a36Sopenharmony_ci } 21362306a36Sopenharmony_ci if (mcam->bus_type == V4L2_MBUS_CSI2_DPHY) { 21462306a36Sopenharmony_ci cam->mipi_clk = devm_clk_get(mcam->dev, "mipi"); 21562306a36Sopenharmony_ci if ((IS_ERR(cam->mipi_clk) && mcam->dphy[2] == 0)) 21662306a36Sopenharmony_ci return PTR_ERR(cam->mipi_clk); 21762306a36Sopenharmony_ci } 21862306a36Sopenharmony_ci mcam->mipi_enabled = false; 21962306a36Sopenharmony_ci mcam->chip_id = MCAM_ARMADA610; 22062306a36Sopenharmony_ci mcam->buffer_mode = B_DMA_sg; 22162306a36Sopenharmony_ci strscpy(mcam->bus_info, "platform:mmp-camera", sizeof(mcam->bus_info)); 22262306a36Sopenharmony_ci spin_lock_init(&mcam->dev_lock); 22362306a36Sopenharmony_ci /* 22462306a36Sopenharmony_ci * Get our I/O memory. 22562306a36Sopenharmony_ci */ 22662306a36Sopenharmony_ci mcam->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); 22762306a36Sopenharmony_ci if (IS_ERR(mcam->regs)) 22862306a36Sopenharmony_ci return PTR_ERR(mcam->regs); 22962306a36Sopenharmony_ci mcam->regs_size = resource_size(res); 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci mcam_init_clk(mcam); 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci /* 23462306a36Sopenharmony_ci * Create a match of the sensor against its OF node. 23562306a36Sopenharmony_ci */ 23662306a36Sopenharmony_ci ep = fwnode_graph_get_next_endpoint(of_fwnode_handle(pdev->dev.of_node), 23762306a36Sopenharmony_ci NULL); 23862306a36Sopenharmony_ci if (!ep) 23962306a36Sopenharmony_ci return -ENODEV; 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci v4l2_async_nf_init(&mcam->notifier, &mcam->v4l2_dev); 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci asd = v4l2_async_nf_add_fwnode_remote(&mcam->notifier, ep, 24462306a36Sopenharmony_ci struct v4l2_async_connection); 24562306a36Sopenharmony_ci fwnode_handle_put(ep); 24662306a36Sopenharmony_ci if (IS_ERR(asd)) { 24762306a36Sopenharmony_ci ret = PTR_ERR(asd); 24862306a36Sopenharmony_ci goto out; 24962306a36Sopenharmony_ci } 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci /* 25262306a36Sopenharmony_ci * Register the device with the core. 25362306a36Sopenharmony_ci */ 25462306a36Sopenharmony_ci ret = mccic_register(mcam); 25562306a36Sopenharmony_ci if (ret) 25662306a36Sopenharmony_ci goto out; 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci /* 25962306a36Sopenharmony_ci * Add OF clock provider. 26062306a36Sopenharmony_ci */ 26162306a36Sopenharmony_ci ret = of_clk_add_provider(pdev->dev.of_node, of_clk_src_simple_get, 26262306a36Sopenharmony_ci mcam->mclk); 26362306a36Sopenharmony_ci if (ret) { 26462306a36Sopenharmony_ci dev_err(&pdev->dev, "can't add DT clock provider\n"); 26562306a36Sopenharmony_ci goto out; 26662306a36Sopenharmony_ci } 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci /* 26962306a36Sopenharmony_ci * Finally, set up our IRQ now that the core is ready to 27062306a36Sopenharmony_ci * deal with it. 27162306a36Sopenharmony_ci */ 27262306a36Sopenharmony_ci ret = platform_get_irq(pdev, 0); 27362306a36Sopenharmony_ci if (ret < 0) 27462306a36Sopenharmony_ci goto out; 27562306a36Sopenharmony_ci cam->irq = ret; 27662306a36Sopenharmony_ci ret = devm_request_irq(&pdev->dev, cam->irq, mmpcam_irq, IRQF_SHARED, 27762306a36Sopenharmony_ci "mmp-camera", mcam); 27862306a36Sopenharmony_ci if (ret) 27962306a36Sopenharmony_ci goto out; 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci pm_runtime_enable(&pdev->dev); 28262306a36Sopenharmony_ci return 0; 28362306a36Sopenharmony_ciout: 28462306a36Sopenharmony_ci mccic_shutdown(mcam); 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci return ret; 28762306a36Sopenharmony_ci} 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_cistatic void mmpcam_remove(struct platform_device *pdev) 29062306a36Sopenharmony_ci{ 29162306a36Sopenharmony_ci struct mmp_camera *cam = platform_get_drvdata(pdev); 29262306a36Sopenharmony_ci struct mcam_camera *mcam = &cam->mcam; 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci mccic_shutdown(mcam); 29562306a36Sopenharmony_ci pm_runtime_force_suspend(mcam->dev); 29662306a36Sopenharmony_ci} 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci/* 29962306a36Sopenharmony_ci * Suspend/resume support. 30062306a36Sopenharmony_ci */ 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_cistatic int __maybe_unused mmpcam_runtime_resume(struct device *dev) 30362306a36Sopenharmony_ci{ 30462306a36Sopenharmony_ci struct mmp_camera *cam = dev_get_drvdata(dev); 30562306a36Sopenharmony_ci struct mcam_camera *mcam = &cam->mcam; 30662306a36Sopenharmony_ci unsigned int i; 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ci for (i = 0; i < NR_MCAM_CLK; i++) { 30962306a36Sopenharmony_ci if (!IS_ERR(mcam->clk[i])) 31062306a36Sopenharmony_ci clk_prepare_enable(mcam->clk[i]); 31162306a36Sopenharmony_ci } 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci return 0; 31462306a36Sopenharmony_ci} 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_cistatic int __maybe_unused mmpcam_runtime_suspend(struct device *dev) 31762306a36Sopenharmony_ci{ 31862306a36Sopenharmony_ci struct mmp_camera *cam = dev_get_drvdata(dev); 31962306a36Sopenharmony_ci struct mcam_camera *mcam = &cam->mcam; 32062306a36Sopenharmony_ci int i; 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ci for (i = NR_MCAM_CLK - 1; i >= 0; i--) { 32362306a36Sopenharmony_ci if (!IS_ERR(mcam->clk[i])) 32462306a36Sopenharmony_ci clk_disable_unprepare(mcam->clk[i]); 32562306a36Sopenharmony_ci } 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_ci return 0; 32862306a36Sopenharmony_ci} 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_cistatic int __maybe_unused mmpcam_suspend(struct device *dev) 33162306a36Sopenharmony_ci{ 33262306a36Sopenharmony_ci struct mmp_camera *cam = dev_get_drvdata(dev); 33362306a36Sopenharmony_ci 33462306a36Sopenharmony_ci if (!pm_runtime_suspended(dev)) 33562306a36Sopenharmony_ci mccic_suspend(&cam->mcam); 33662306a36Sopenharmony_ci return 0; 33762306a36Sopenharmony_ci} 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_cistatic int __maybe_unused mmpcam_resume(struct device *dev) 34062306a36Sopenharmony_ci{ 34162306a36Sopenharmony_ci struct mmp_camera *cam = dev_get_drvdata(dev); 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_ci if (!pm_runtime_suspended(dev)) 34462306a36Sopenharmony_ci return mccic_resume(&cam->mcam); 34562306a36Sopenharmony_ci return 0; 34662306a36Sopenharmony_ci} 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_cistatic const struct dev_pm_ops mmpcam_pm_ops = { 34962306a36Sopenharmony_ci SET_RUNTIME_PM_OPS(mmpcam_runtime_suspend, mmpcam_runtime_resume, NULL) 35062306a36Sopenharmony_ci SET_SYSTEM_SLEEP_PM_OPS(mmpcam_suspend, mmpcam_resume) 35162306a36Sopenharmony_ci}; 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_cistatic const struct of_device_id mmpcam_of_match[] = { 35462306a36Sopenharmony_ci { .compatible = "marvell,mmp2-ccic", }, 35562306a36Sopenharmony_ci {}, 35662306a36Sopenharmony_ci}; 35762306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, mmpcam_of_match); 35862306a36Sopenharmony_ci 35962306a36Sopenharmony_cistatic struct platform_driver mmpcam_driver = { 36062306a36Sopenharmony_ci .probe = mmpcam_probe, 36162306a36Sopenharmony_ci .remove_new = mmpcam_remove, 36262306a36Sopenharmony_ci .driver = { 36362306a36Sopenharmony_ci .name = "mmp-camera", 36462306a36Sopenharmony_ci .of_match_table = mmpcam_of_match, 36562306a36Sopenharmony_ci .pm = &mmpcam_pm_ops, 36662306a36Sopenharmony_ci } 36762306a36Sopenharmony_ci}; 36862306a36Sopenharmony_ci 36962306a36Sopenharmony_cimodule_platform_driver(mmpcam_driver); 370