diff options
Diffstat (limited to 'drivers')
231 files changed, 21705 insertions, 2478 deletions
diff --git a/drivers/leds/leds-aat1290.c b/drivers/leds/leds-aat1290.c index def3cf9f7e92..a21e19297745 100644 --- a/drivers/leds/leds-aat1290.c +++ b/drivers/leds/leds-aat1290.c @@ -503,8 +503,9 @@ static int aat1290_led_probe(struct platform_device *pdev) aat1290_init_v4l2_flash_config(led, &led_cfg, &v4l2_sd_cfg); /* Create V4L2 Flash subdev. */ - led->v4l2_flash = v4l2_flash_init(dev, sub_node, fled_cdev, NULL, - &v4l2_flash_ops, &v4l2_sd_cfg); + led->v4l2_flash = v4l2_flash_init(dev, of_fwnode_handle(sub_node), + fled_cdev, NULL, &v4l2_flash_ops, + &v4l2_sd_cfg); if (IS_ERR(led->v4l2_flash)) { ret = PTR_ERR(led->v4l2_flash); goto error_v4l2_flash_init; diff --git a/drivers/leds/leds-max77693.c b/drivers/leds/leds-max77693.c index 1eb58ef6aefe..2d3062d53325 100644 --- a/drivers/leds/leds-max77693.c +++ b/drivers/leds/leds-max77693.c @@ -930,8 +930,9 @@ static int max77693_register_led(struct max77693_sub_led *sub_led, max77693_init_v4l2_flash_config(sub_led, led_cfg, &v4l2_sd_cfg); /* Register in the V4L2 subsystem. */ - sub_led->v4l2_flash = v4l2_flash_init(dev, sub_node, fled_cdev, NULL, - &v4l2_flash_ops, &v4l2_sd_cfg); + sub_led->v4l2_flash = v4l2_flash_init(dev, of_fwnode_handle(sub_node), + fled_cdev, NULL, &v4l2_flash_ops, + &v4l2_sd_cfg); if (IS_ERR(sub_led->v4l2_flash)) { ret = PTR_ERR(sub_led->v4l2_flash); goto err_v4l2_flash_init; diff --git a/drivers/media/cec/cec-adap.c b/drivers/media/cec/cec-adap.c index 9dfc79800c71..bf45977b2823 100644 --- a/drivers/media/cec/cec-adap.c +++ b/drivers/media/cec/cec-adap.c @@ -28,6 +28,8 @@ #include <linux/string.h> #include <linux/types.h> +#include <drm/drm_edid.h> + #include "cec-priv.h" static void cec_fill_msg_report_features(struct cec_adapter *adap, @@ -366,6 +368,8 @@ int cec_thread_func(void *_adap) * transmit should be canceled. */ err = wait_event_interruptible_timeout(adap->kthread_waitq, + (adap->needs_hpd && + (!adap->is_configured && !adap->is_configuring)) || kthread_should_stop() || (!adap->transmitting && !list_empty(&adap->transmit_queue)), @@ -381,7 +385,9 @@ int cec_thread_func(void *_adap) mutex_lock(&adap->lock); - if (kthread_should_stop()) { + if ((adap->needs_hpd && + (!adap->is_configured && !adap->is_configuring)) || + kthread_should_stop()) { cec_flush(adap); goto unlock; } @@ -392,7 +398,7 @@ int cec_thread_func(void *_adap) * happen and is an indication of a faulty CEC adapter * driver, or the CEC bus is in some weird state. */ - dprintk(0, "message %*ph timed out!\n", + dprintk(0, "%s: message %*ph timed out!\n", __func__, adap->transmitting->msg.len, adap->transmitting->msg.msg); /* Just give up on this. */ @@ -468,7 +474,7 @@ void cec_transmit_done(struct cec_adapter *adap, u8 status, u8 arb_lost_cnt, struct cec_msg *msg; u64 ts = ktime_get_ns(); - dprintk(2, "cec_transmit_done %02x\n", status); + dprintk(2, "%s: status %02x\n", __func__, status); mutex_lock(&adap->lock); data = adap->transmitting; if (!data) { @@ -477,7 +483,8 @@ void cec_transmit_done(struct cec_adapter *adap, u8 status, u8 arb_lost_cnt, * unplugged while the transmit is ongoing. Ignore this * transmit in that case. */ - dprintk(1, "cec_transmit_done without an ongoing transmit!\n"); + dprintk(1, "%s was called without an ongoing transmit!\n", + __func__); goto unlock; } @@ -504,6 +511,12 @@ void cec_transmit_done(struct cec_adapter *adap, u8 status, u8 arb_lost_cnt, !(status & (CEC_TX_STATUS_MAX_RETRIES | CEC_TX_STATUS_OK))) { /* Retry this message */ data->attempts--; + if (msg->timeout) + dprintk(2, "retransmit: %*ph (attempts: %d, wait for 0x%02x)\n", + msg->len, msg->msg, data->attempts, msg->reply); + else + dprintk(2, "retransmit: %*ph (attempts: %d)\n", + msg->len, msg->msg, data->attempts); /* Add the message in front of the transmit queue */ list_add(&data->list, &adap->transmit_queue); adap->transmit_queue_sz++; @@ -544,6 +557,32 @@ unlock: } EXPORT_SYMBOL_GPL(cec_transmit_done); +void cec_transmit_attempt_done(struct cec_adapter *adap, u8 status) +{ + switch (status) { + case CEC_TX_STATUS_OK: + cec_transmit_done(adap, status, 0, 0, 0, 0); + return; + case CEC_TX_STATUS_ARB_LOST: + cec_transmit_done(adap, status, 1, 0, 0, 0); + return; + case CEC_TX_STATUS_NACK: + cec_transmit_done(adap, status, 0, 1, 0, 0); + return; + case CEC_TX_STATUS_LOW_DRIVE: + cec_transmit_done(adap, status, 0, 0, 1, 0); + return; + case CEC_TX_STATUS_ERROR: + cec_transmit_done(adap, status, 0, 0, 0, 1); + return; + default: + /* Should never happen */ + WARN(1, "cec-%s: invalid status 0x%02x\n", adap->name, status); + return; + } +} +EXPORT_SYMBOL_GPL(cec_transmit_attempt_done); + /* * Called when waiting for a reply times out. */ @@ -647,7 +686,7 @@ int cec_transmit_msg_fh(struct cec_adapter *adap, struct cec_msg *msg, return -EINVAL; } if (!adap->is_configured && !adap->is_configuring) { - if (msg->msg[0] != 0xf0) { + if (adap->needs_hpd || msg->msg[0] != 0xf0) { dprintk(1, "%s: adapter is unconfigured\n", __func__); return -ENONET; } @@ -911,7 +950,7 @@ void cec_received_msg(struct cec_adapter *adap, struct cec_msg *msg) memset(msg->msg + msg->len, 0, sizeof(msg->msg) - msg->len); mutex_lock(&adap->lock); - dprintk(2, "cec_received_msg: %*ph\n", msg->len, msg->msg); + dprintk(2, "%s: %*ph\n", __func__, msg->len, msg->msg); /* Check if this message was for us (directed or broadcast). */ if (!cec_msg_is_broadcast(msg)) @@ -1112,9 +1151,6 @@ static int cec_config_log_addr(struct cec_adapter *adap, las->log_addr[idx] = log_addr; las->log_addr_mask |= 1 << log_addr; adap->phys_addrs[log_addr] = adap->phys_addr; - - dprintk(2, "claimed addr %d (%d)\n", log_addr, - las->primary_device_type[idx]); return 1; } @@ -1126,7 +1162,9 @@ static int cec_config_log_addr(struct cec_adapter *adap, */ static void cec_adap_unconfigure(struct cec_adapter *adap) { - WARN_ON(adap->ops->adap_log_addr(adap, CEC_LOG_ADDR_INVALID)); + if (!adap->needs_hpd || + adap->phys_addr != CEC_PHYS_ADDR_INVALID) + WARN_ON(adap->ops->adap_log_addr(adap, CEC_LOG_ADDR_INVALID)); adap->log_addrs.log_addr_mask = 0; adap->is_configuring = false; adap->is_configured = false; @@ -1300,7 +1338,7 @@ configured: /* Report Physical Address */ cec_msg_report_physical_addr(&msg, adap->phys_addr, las->primary_device_type[i]); - dprintk(2, "config: la %d pa %x.%x.%x.%x\n", + dprintk(1, "config: la %d pa %x.%x.%x.%x\n", las->log_addr[i], cec_phys_addr_exp(adap->phys_addr)); cec_transmit_msg_fh(adap, &msg, NULL, false); @@ -1355,6 +1393,8 @@ void __cec_s_phys_addr(struct cec_adapter *adap, u16 phys_addr, bool block) if (phys_addr == adap->phys_addr || adap->devnode.unregistered) return; + dprintk(1, "new physical address %x.%x.%x.%x\n", + cec_phys_addr_exp(phys_addr)); if (phys_addr == CEC_PHYS_ADDR_INVALID || adap->phys_addr != CEC_PHYS_ADDR_INVALID) { adap->phys_addr = CEC_PHYS_ADDR_INVALID; @@ -1364,7 +1404,7 @@ void __cec_s_phys_addr(struct cec_adapter *adap, u16 phys_addr, bool block) if (adap->monitor_all_cnt) WARN_ON(call_op(adap, adap_monitor_all_enable, false)); mutex_lock(&adap->devnode.lock); - if (list_empty(&adap->devnode.fhs)) + if (adap->needs_hpd || list_empty(&adap->devnode.fhs)) WARN_ON(adap->ops->adap_enable(adap, false)); mutex_unlock(&adap->devnode.lock); if (phys_addr == CEC_PHYS_ADDR_INVALID) @@ -1372,7 +1412,7 @@ void __cec_s_phys_addr(struct cec_adapter *adap, u16 phys_addr, bool block) } mutex_lock(&adap->devnode.lock); - if (list_empty(&adap->devnode.fhs) && + if ((adap->needs_hpd || list_empty(&adap->devnode.fhs)) && adap->ops->adap_enable(adap, true)) { mutex_unlock(&adap->devnode.lock); return; @@ -1380,7 +1420,7 @@ void __cec_s_phys_addr(struct cec_adapter *adap, u16 phys_addr, bool block) if (adap->monitor_all_cnt && call_op(adap, adap_monitor_all_enable, true)) { - if (list_empty(&adap->devnode.fhs)) + if (adap->needs_hpd || list_empty(&adap->devnode.fhs)) WARN_ON(adap->ops->adap_enable(adap, false)); mutex_unlock(&adap->devnode.lock); return; @@ -1404,6 +1444,18 @@ void cec_s_phys_addr(struct cec_adapter *adap, u16 phys_addr, bool block) } EXPORT_SYMBOL_GPL(cec_s_phys_addr); +void cec_s_phys_addr_from_edid(struct cec_adapter *adap, + const struct edid *edid) +{ + u16 pa = CEC_PHYS_ADDR_INVALID; + + if (edid && edid->extensions) + pa = cec_get_edid_phys_addr((const u8 *)edid, + EDID_LENGTH * (edid->extensions + 1), NULL); + cec_s_phys_addr(adap, pa, false); +} +EXPORT_SYMBOL_GPL(cec_s_phys_addr_from_edid); + /* * Called from either the ioctl or a driver to set the logical addresses. * @@ -1534,12 +1586,12 @@ int __cec_s_log_addrs(struct cec_adapter *adap, if (log_addrs->num_log_addrs == 2) { if (!(type_mask & ((1 << CEC_LOG_ADDR_TYPE_AUDIOSYSTEM) | (1 << CEC_LOG_ADDR_TYPE_TV)))) { - dprintk(1, "Two LAs is only allowed for audiosystem and TV\n"); + dprintk(1, "two LAs is only allowed for audiosystem and TV\n"); return -EINVAL; } if (!(type_mask & ((1 << CEC_LOG_ADDR_TYPE_PLAYBACK) | (1 << CEC_LOG_ADDR_TYPE_RECORD)))) { - dprintk(1, "An audiosystem/TV can only be combined with record or playback\n"); + dprintk(1, "an audiosystem/TV can only be combined with record or playback\n"); return -EINVAL; } } @@ -1653,7 +1705,7 @@ static int cec_receive_notify(struct cec_adapter *adap, struct cec_msg *msg, bool from_unregistered = init_laddr == 0xf; struct cec_msg tx_cec_msg = { }; - dprintk(1, "cec_receive_notify: %*ph\n", msg->len, msg->msg); + dprintk(2, "%s: %*ph\n", __func__, msg->len, msg->msg); /* If this is a CDC-Only device, then ignore any non-CDC messages */ if (cec_is_cdc_only(&adap->log_addrs) && @@ -1722,7 +1774,7 @@ static int cec_receive_notify(struct cec_adapter *adap, struct cec_msg *msg, if (!from_unregistered) adap->phys_addrs[init_laddr] = pa; - dprintk(1, "Reported physical address %x.%x.%x.%x for logical address %d\n", + dprintk(1, "reported physical address %x.%x.%x.%x for logical address %d\n", cec_phys_addr_exp(pa), init_laddr); break; } diff --git a/drivers/media/cec/cec-api.c b/drivers/media/cec/cec-api.c index 999926f731c8..f7eb4c54a354 100644 --- a/drivers/media/cec/cec-api.c +++ b/drivers/media/cec/cec-api.c @@ -202,7 +202,8 @@ static long cec_transmit(struct cec_adapter *adap, struct cec_fh *fh, err = -EPERM; else if (adap->is_configuring) err = -ENONET; - else if (!adap->is_configured && msg.msg[0] != 0xf0) + else if (!adap->is_configured && + (adap->needs_hpd || msg.msg[0] != 0xf0)) err = -ENONET; else if (cec_is_busy(adap, fh)) err = -EBUSY; @@ -515,6 +516,7 @@ static int cec_open(struct inode *inode, struct file *filp) mutex_lock(&devnode->lock); if (list_empty(&devnode->fhs) && + !adap->needs_hpd && adap->phys_addr == CEC_PHYS_ADDR_INVALID) { err = adap->ops->adap_enable(adap, true); if (err) { @@ -559,6 +561,7 @@ static int cec_release(struct inode *inode, struct file *filp) mutex_lock(&devnode->lock); list_del(&fh->list); if (list_empty(&devnode->fhs) && + !adap->needs_hpd && adap->phys_addr == CEC_PHYS_ADDR_INVALID) { WARN_ON(adap->ops->adap_enable(adap, false)); } diff --git a/drivers/media/cec/cec-core.c b/drivers/media/cec/cec-core.c index 2f87748ba4fc..b516d599d6c4 100644 --- a/drivers/media/cec/cec-core.c +++ b/drivers/media/cec/cec-core.c @@ -230,6 +230,7 @@ struct cec_adapter *cec_allocate_adapter(const struct cec_adap_ops *ops, adap->log_addrs.cec_version = CEC_OP_CEC_VERSION_2_0; adap->log_addrs.vendor_id = CEC_VENDOR_ID_NONE; adap->capabilities = caps; + adap->needs_hpd = caps & CEC_CAP_NEEDS_HPD; adap->available_log_addrs = available_las; adap->sequence = 0; adap->ops = ops; diff --git a/drivers/media/dvb-core/dvb_ca_en50221.c b/drivers/media/dvb-core/dvb_ca_en50221.c index d38bf9bce480..af694f2066a2 100644 --- a/drivers/media/dvb-core/dvb_ca_en50221.c +++ b/drivers/media/dvb-core/dvb_ca_en50221.c @@ -193,8 +193,10 @@ static void dvb_ca_private_put(struct dvb_ca_private *ca) } static void dvb_ca_en50221_thread_wakeup(struct dvb_ca_private *ca); -static int dvb_ca_en50221_read_data(struct dvb_ca_private *ca, int slot, u8 * ebuf, int ecount); -static int dvb_ca_en50221_write_data(struct dvb_ca_private *ca, int slot, u8 * ebuf, int ecount); +static int dvb_ca_en50221_read_data(struct dvb_ca_private *ca, int slot, + u8 *ebuf, int ecount); +static int dvb_ca_en50221_write_data(struct dvb_ca_private *ca, int slot, + u8 *ebuf, int ecount); /** @@ -206,7 +208,7 @@ static int dvb_ca_en50221_write_data(struct dvb_ca_private *ca, int slot, u8 * e * @nlen: Number of bytes in needle. * @return Pointer into haystack needle was found at, or NULL if not found. */ -static char *findstr(char * haystack, int hlen, char * needle, int nlen) +static char *findstr(char *haystack, int hlen, char *needle, int nlen) { int i; @@ -390,7 +392,8 @@ static int dvb_ca_en50221_link_init(struct dvb_ca_private *ca, int slot) * @return 0 on success, nonzero on error. */ static int dvb_ca_en50221_read_tuple(struct dvb_ca_private *ca, int slot, - int *address, int *tupleType, int *tupleLength, u8 * tuple) + int *address, int *tupleType, + int *tupleLength, u8 *tuple) { int i; int _tupleType; @@ -621,7 +624,8 @@ static int dvb_ca_en50221_set_configoption(struct dvb_ca_private *ca, int slot) * * @return Number of bytes read, or < 0 on error */ -static int dvb_ca_en50221_read_data(struct dvb_ca_private *ca, int slot, u8 * ebuf, int ecount) +static int dvb_ca_en50221_read_data(struct dvb_ca_private *ca, int slot, + u8 *ebuf, int ecount) { int bytes_read; int status; @@ -745,7 +749,8 @@ exit: * * @return Number of bytes written, or < 0 on error. */ -static int dvb_ca_en50221_write_data(struct dvb_ca_private *ca, int slot, u8 * buf, int bytes_write) +static int dvb_ca_en50221_write_data(struct dvb_ca_private *ca, int slot, + u8 *buf, int bytes_write) { int status; int i; @@ -840,7 +845,6 @@ exit: exitnowrite: return status; } -EXPORT_SYMBOL(dvb_ca_en50221_camchange_irq); @@ -849,7 +853,7 @@ EXPORT_SYMBOL(dvb_ca_en50221_camchange_irq); /** - * dvb_ca_en50221_camready_irq - A CAM has been removed => shut it down. + * dvb_ca_en50221_slot_shutdown - A CAM has been removed => shut it down. * * @ca: CA instance. * @slot: Slot to shut down. @@ -870,11 +874,10 @@ static int dvb_ca_en50221_slot_shutdown(struct dvb_ca_private *ca, int slot) /* success */ return 0; } -EXPORT_SYMBOL(dvb_ca_en50221_camready_irq); /** - * dvb_ca_en50221_camready_irq - A CAMCHANGE IRQ has occurred. + * dvb_ca_en50221_camchange_irq - A CAMCHANGE IRQ has occurred. * * @ca: CA instance. * @slot: Slot concerned. @@ -899,7 +902,7 @@ void dvb_ca_en50221_camchange_irq(struct dvb_ca_en50221 *pubca, int slot, int ch atomic_inc(&ca->slot_info[slot].camchange_count); dvb_ca_en50221_thread_wakeup(ca); } -EXPORT_SYMBOL(dvb_ca_en50221_frda_irq); +EXPORT_SYMBOL(dvb_ca_en50221_camchange_irq); /** @@ -919,10 +922,11 @@ void dvb_ca_en50221_camready_irq(struct dvb_ca_en50221 *pubca, int slot) dvb_ca_en50221_thread_wakeup(ca); } } +EXPORT_SYMBOL(dvb_ca_en50221_camready_irq); /** - * An FR or DA IRQ has occurred. + * dvb_ca_en50221_frda_irq - An FR or DA IRQ has occurred. * * @ca: CA instance. * @slot: Slot concerned. @@ -949,7 +953,7 @@ void dvb_ca_en50221_frda_irq(struct dvb_ca_en50221 *pubca, int slot) break; } } - +EXPORT_SYMBOL(dvb_ca_en50221_frda_irq); /* ******************************************************************************** */ @@ -1345,7 +1349,8 @@ static long dvb_ca_en50221_io_ioctl(struct file *file, * @return Number of bytes read, or <0 on error. */ static ssize_t dvb_ca_en50221_io_write(struct file *file, - const char __user * buf, size_t count, loff_t * ppos) + const char __user *buf, size_t count, + loff_t *ppos) { struct dvb_device *dvbdev = file->private_data; struct dvb_ca_private *ca = dvbdev->priv; @@ -1485,8 +1490,8 @@ nextslot: * * @return Number of bytes read, or <0 on error. */ -static ssize_t dvb_ca_en50221_io_read(struct file *file, char __user * buf, - size_t count, loff_t * ppos) +static ssize_t dvb_ca_en50221_io_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) { struct dvb_device *dvbdev = file->private_data; struct dvb_ca_private *ca = dvbdev->priv; @@ -1664,7 +1669,7 @@ static int dvb_ca_en50221_io_release(struct inode *inode, struct file *file) * * @return Standard poll mask. */ -static unsigned int dvb_ca_en50221_io_poll(struct file *file, poll_table * wait) +static unsigned int dvb_ca_en50221_io_poll(struct file *file, poll_table *wait) { struct dvb_device *dvbdev = file->private_data; struct dvb_ca_private *ca = dvbdev->priv; diff --git a/drivers/media/dvb-frontends/af9013.c b/drivers/media/dvb-frontends/af9013.c index b978002af4d8..51f942fc0669 100644 --- a/drivers/media/dvb-frontends/af9013.c +++ b/drivers/media/dvb-frontends/af9013.c @@ -535,6 +535,7 @@ static void af9013_statistics_work(struct work_struct *work) switch (state->statistics_step) { default: state->statistics_step = 0; + /* fall-through */ case 0: af9013_statistics_signal_strength(&state->fe); state->statistics_step++; diff --git a/drivers/media/dvb-frontends/au8522_common.c b/drivers/media/dvb-frontends/au8522_common.c index cf4ac240a01f..6722838c3707 100644 --- a/drivers/media/dvb-frontends/au8522_common.c +++ b/drivers/media/dvb-frontends/au8522_common.c @@ -234,6 +234,7 @@ int au8522_init(struct dvb_frontend *fe) chip, so that when it gets powered back up it won't think that it is already tuned */ state->current_frequency = 0; + state->current_modulation = VSB_8; au8522_writereg(state, 0xa4, 1 << 5); diff --git a/drivers/media/dvb-frontends/au8522_decoder.c b/drivers/media/dvb-frontends/au8522_decoder.c index a2e771305008..343dc92ef54e 100644 --- a/drivers/media/dvb-frontends/au8522_decoder.c +++ b/drivers/media/dvb-frontends/au8522_decoder.c @@ -17,7 +17,6 @@ /* Developer notes: * - * VBI support is not yet working * Enough is implemented here for CVBS and S-Video inputs, but the actual * analog demodulator code isn't implemented (not needed for xc5000 since it * has its own demodulator and outputs CVBS) @@ -179,42 +178,6 @@ static inline struct au8522_state *to_state(struct v4l2_subdev *sd) return container_of(sd, struct au8522_state, sd); } -static void setup_vbi(struct au8522_state *state, int aud_input) -{ - int i; - - /* These are set to zero regardless of what mode we're in */ - au8522_writereg(state, AU8522_TVDEC_VBI_CTRL_H_REG017H, 0x00); - au8522_writereg(state, AU8522_TVDEC_VBI_CTRL_L_REG018H, 0x00); - au8522_writereg(state, AU8522_TVDEC_VBI_USER_TOTAL_BITS_REG019H, 0x00); - au8522_writereg(state, AU8522_TVDEC_VBI_USER_TUNIT_H_REG01AH, 0x00); - au8522_writereg(state, AU8522_TVDEC_VBI_USER_TUNIT_L_REG01BH, 0x00); - au8522_writereg(state, AU8522_TVDEC_VBI_USER_THRESH1_REG01CH, 0x00); - au8522_writereg(state, AU8522_TVDEC_VBI_USER_FRAME_PAT2_REG01EH, 0x00); - au8522_writereg(state, AU8522_TVDEC_VBI_USER_FRAME_PAT1_REG01FH, 0x00); - au8522_writereg(state, AU8522_TVDEC_VBI_USER_FRAME_PAT0_REG020H, 0x00); - au8522_writereg(state, AU8522_TVDEC_VBI_USER_FRAME_MASK2_REG021H, - 0x00); - au8522_writereg(state, AU8522_TVDEC_VBI_USER_FRAME_MASK1_REG022H, - 0x00); - au8522_writereg(state, AU8522_TVDEC_VBI_USER_FRAME_MASK0_REG023H, - 0x00); - - /* Setup the VBI registers */ - for (i = 0x30; i < 0x60; i++) - au8522_writereg(state, i, 0x40); - - /* For some reason, every register is 0x40 except register 0x44 - (confirmed via the HVR-950q USB capture) */ - au8522_writereg(state, 0x44, 0x60); - - /* Enable VBI (we always do this regardless of whether the user is - viewing closed caption info) */ - au8522_writereg(state, AU8522_TVDEC_VBI_CTRL_H_REG017H, - AU8522_TVDEC_VBI_CTRL_H_REG017H_CCON); - -} - static void setup_decoder_defaults(struct au8522_state *state, bool is_svideo) { int i; @@ -317,8 +280,6 @@ static void setup_decoder_defaults(struct au8522_state *state, bool is_svideo) AU8522_TOREGAAGC_REG0E5H_CVBS); au8522_writereg(state, AU8522_REG016H, AU8522_REG016H_CVBS); - setup_vbi(state, 0); - if (is_svideo) { /* Despite what the table says, for the HVR-950q we still need to be in CVBS mode for the S-Video input (reason unknown). */ @@ -456,30 +417,29 @@ static void set_audio_input(struct au8522_state *state) lpfilter_coef[i].reg_val[0]); } - /* Setup audio */ - au8522_writereg(state, AU8522_AUDIO_VOLUME_L_REG0F2H, 0x00); - au8522_writereg(state, AU8522_AUDIO_VOLUME_R_REG0F3H, 0x00); - au8522_writereg(state, AU8522_AUDIO_VOLUME_REG0F4H, 0x00); - au8522_writereg(state, AU8522_I2C_CONTROL_REG1_REG091H, 0x80); - au8522_writereg(state, AU8522_I2C_CONTROL_REG0_REG090H, 0x84); - msleep(150); - au8522_writereg(state, AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H, 0x00); - msleep(10); - au8522_writereg(state, AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H, - AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H_CVBS); - msleep(50); + /* Set the volume */ au8522_writereg(state, AU8522_AUDIO_VOLUME_L_REG0F2H, 0x7F); au8522_writereg(state, AU8522_AUDIO_VOLUME_R_REG0F3H, 0x7F); au8522_writereg(state, AU8522_AUDIO_VOLUME_REG0F4H, 0xff); - msleep(80); - au8522_writereg(state, AU8522_AUDIO_VOLUME_L_REG0F2H, 0x7F); - au8522_writereg(state, AU8522_AUDIO_VOLUME_R_REG0F3H, 0x7F); + + /* Not sure what this does */ au8522_writereg(state, AU8522_REG0F9H, AU8522_REG0F9H_AUDIO); + + /* Setup the audio mode to stereo DBX */ au8522_writereg(state, AU8522_AUDIO_MODE_REG0F1H, 0x82); msleep(70); - au8522_writereg(state, AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H, 0x09); + + /* Start the audio processing module */ + au8522_writereg(state, AU8522_SYSTEM_MODULE_CONTROL_0_REG0A4H, 0x9d); + + /* Set the audio frequency to 48 KHz */ au8522_writereg(state, AU8522_AUDIOFREQ_REG606H, 0x03); + + /* Set the I2S parameters (WS, LSB, mode, sample rate */ au8522_writereg(state, AU8522_I2S_CTRL_2_REG112H, 0xc2); + + /* Enable the I2S output */ + au8522_writereg(state, AU8522_SYSTEM_MODULE_CONTROL_1_REG0A5H, 0x09); } /* ----------------------------------------------------------------------- */ @@ -663,10 +623,12 @@ static int au8522_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt) int val = 0; struct au8522_state *state = to_state(sd); u8 lock_status; + u8 pll_status; /* Interrogate the decoder to see if we are getting a real signal */ lock_status = au8522_readreg(state, 0x00); - if (lock_status == 0xa2) + pll_status = au8522_readreg(state, 0x7e); + if ((lock_status == 0xa2) && (pll_status & 0x10)) vt->signal = 0xffff; else vt->signal = 0x00; diff --git a/drivers/media/dvb-frontends/au8522_dig.c b/drivers/media/dvb-frontends/au8522_dig.c index 7ed326e43fc4..3f3635f5a06a 100644 --- a/drivers/media/dvb-frontends/au8522_dig.c +++ b/drivers/media/dvb-frontends/au8522_dig.c @@ -271,9 +271,9 @@ static int au8522_set_if(struct dvb_frontend *fe, enum au8522_if_freq if_freq) return -EINVAL; } dprintk("%s() %s MHz\n", __func__, ifmhz); - au8522_writereg(state, 0x80b5, r0b5); - au8522_writereg(state, 0x80b6, r0b6); - au8522_writereg(state, 0x80b7, r0b7); + au8522_writereg(state, 0x00b5, r0b5); + au8522_writereg(state, 0x00b6, r0b6); + au8522_writereg(state, 0x00b7, r0b7); return 0; } @@ -283,33 +283,32 @@ static struct { u16 reg; u16 data; } VSB_mod_tab[] = { - { 0x8090, 0x84 }, - { 0x4092, 0x11 }, + { 0x0090, 0x84 }, { 0x2005, 0x00 }, - { 0x8091, 0x80 }, - { 0x80a3, 0x0c }, - { 0x80a4, 0xe8 }, - { 0x8081, 0xc4 }, - { 0x80a5, 0x40 }, - { 0x80a7, 0x40 }, - { 0x80a6, 0x67 }, - { 0x8262, 0x20 }, - { 0x821c, 0x30 }, - { 0x80d8, 0x1a }, - { 0x8227, 0xa0 }, - { 0x8121, 0xff }, - { 0x80a8, 0xf0 }, - { 0x80a9, 0x05 }, - { 0x80aa, 0x77 }, - { 0x80ab, 0xf0 }, - { 0x80ac, 0x05 }, - { 0x80ad, 0x77 }, - { 0x80ae, 0x41 }, - { 0x80af, 0x66 }, - { 0x821b, 0xcc }, - { 0x821d, 0x80 }, - { 0x80a4, 0xe8 }, - { 0x8231, 0x13 }, + { 0x0091, 0x80 }, + { 0x00a3, 0x0c }, + { 0x00a4, 0xe8 }, + { 0x0081, 0xc4 }, + { 0x00a5, 0x40 }, + { 0x00a7, 0x40 }, + { 0x00a6, 0x67 }, + { 0x0262, 0x20 }, + { 0x021c, 0x30 }, + { 0x00d8, 0x1a }, + { 0x0227, 0xa0 }, + { 0x0121, 0xff }, + { 0x00a8, 0xf0 }, + { 0x00a9, 0x05 }, + { 0x00aa, 0x77 }, + { 0x00ab, 0xf0 }, + { 0x00ac, 0x05 }, + { 0x00ad, 0x77 }, + { 0x00ae, 0x41 }, + { 0x00af, 0x66 }, + { 0x021b, 0xcc }, + { 0x021d, 0x80 }, + { 0x00a4, 0xe8 }, + { 0x0231, 0x13 }, }; /* QAM64 Modulation table */ @@ -396,78 +395,78 @@ static struct { u16 reg; u16 data; } QAM256_mod_tab[] = { - { 0x80a3, 0x09 }, - { 0x80a4, 0x00 }, - { 0x8081, 0xc4 }, - { 0x80a5, 0x40 }, - { 0x80aa, 0x77 }, - { 0x80ad, 0x77 }, - { 0x80a6, 0x67 }, - { 0x8262, 0x20 }, - { 0x821c, 0x30 }, - { 0x80b8, 0x3e }, - { 0x80b9, 0xf0 }, - { 0x80ba, 0x01 }, - { 0x80bb, 0x18 }, - { 0x80bc, 0x50 }, - { 0x80bd, 0x00 }, - { 0x80be, 0xea }, - { 0x80bf, 0xef }, - { 0x80c0, 0xfc }, - { 0x80c1, 0xbd }, - { 0x80c2, 0x1f }, - { 0x80c3, 0xfc }, - { 0x80c4, 0xdd }, - { 0x80c5, 0xaf }, - { 0x80c6, 0x00 }, - { 0x80c7, 0x38 }, - { 0x80c8, 0x30 }, - { 0x80c9, 0x05 }, - { 0x80ca, 0x4a }, - { 0x80cb, 0xd0 }, - { 0x80cc, 0x01 }, - { 0x80cd, 0xd9 }, - { 0x80ce, 0x6f }, - { 0x80cf, 0xf9 }, - { 0x80d0, 0x70 }, - { 0x80d1, 0xdf }, - { 0x80d2, 0xf7 }, - { 0x80d3, 0xc2 }, - { 0x80d4, 0xdf }, - { 0x80d5, 0x02 }, - { 0x80d6, 0x9a }, - { 0x80d7, 0xd0 }, - { 0x8250, 0x0d }, - { 0x8251, 0xcd }, - { 0x8252, 0xe0 }, - { 0x8253, 0x05 }, - { 0x8254, 0xa7 }, - { 0x8255, 0xff }, - { 0x8256, 0xed }, - { 0x8257, 0x5b }, - { 0x8258, 0xae }, - { 0x8259, 0xe6 }, - { 0x825a, 0x3d }, - { 0x825b, 0x0f }, - { 0x825c, 0x0d }, - { 0x825d, 0xea }, - { 0x825e, 0xf2 }, - { 0x825f, 0x51 }, - { 0x8260, 0xf5 }, - { 0x8261, 0x06 }, - { 0x821a, 0x00 }, - { 0x8546, 0x40 }, - { 0x8210, 0x26 }, - { 0x8211, 0xf6 }, - { 0x8212, 0x84 }, - { 0x8213, 0x02 }, - { 0x8502, 0x01 }, - { 0x8121, 0x04 }, - { 0x8122, 0x04 }, - { 0x852e, 0x10 }, - { 0x80a4, 0xca }, - { 0x80a7, 0x40 }, - { 0x8526, 0x01 }, + { 0x00a3, 0x09 }, + { 0x00a4, 0x00 }, + { 0x0081, 0xc4 }, + { 0x00a5, 0x40 }, + { 0x00aa, 0x77 }, + { 0x00ad, 0x77 }, + { 0x00a6, 0x67 }, + { 0x0262, 0x20 }, + { 0x021c, 0x30 }, + { 0x00b8, 0x3e }, + { 0x00b9, 0xf0 }, + { 0x00ba, 0x01 }, + { 0x00bb, 0x18 }, + { 0x00bc, 0x50 }, + { 0x00bd, 0x00 }, + { 0x00be, 0xea }, + { 0x00bf, 0xef }, + { 0x00c0, 0xfc }, + { 0x00c1, 0xbd }, + { 0x00c2, 0x1f }, + { 0x00c3, 0xfc }, + { 0x00c4, 0xdd }, + { 0x00c5, 0xaf }, + { 0x00c6, 0x00 }, + { 0x00c7, 0x38 }, + { 0x00c8, 0x30 }, + { 0x00c9, 0x05 }, + { 0x00ca, 0x4a }, + { 0x00cb, 0xd0 }, + { 0x00cc, 0x01 }, + { 0x00cd, 0xd9 }, + { 0x00ce, 0x6f }, + { 0x00cf, 0xf9 }, + { 0x00d0, 0x70 }, + { 0x00d1, 0xdf }, + { 0x00d2, 0xf7 }, + { 0x00d3, 0xc2 }, + { 0x00d4, 0xdf }, + { 0x00d5, 0x02 }, + { 0x00d6, 0x9a }, + { 0x00d7, 0xd0 }, + { 0x0250, 0x0d }, + { 0x0251, 0xcd }, + { 0x0252, 0xe0 }, + { 0x0253, 0x05 }, + { 0x0254, 0xa7 }, + { 0x0255, 0xff }, + { 0x0256, 0xed }, + { 0x0257, 0x5b }, + { 0x0258, 0xae }, + { 0x0259, 0xe6 }, + { 0x025a, 0x3d }, + { 0x025b, 0x0f }, + { 0x025c, 0x0d }, + { 0x025d, 0xea }, + { 0x025e, 0xf2 }, + { 0x025f, 0x51 }, + { 0x0260, 0xf5 }, + { 0x0261, 0x06 }, + { 0x021a, 0x00 }, + { 0x0546, 0x40 }, + { 0x0210, 0x26 }, + { 0x0211, 0xf6 }, + { 0x0212, 0x84 }, + { 0x0213, 0x02 }, + { 0x0502, 0x01 }, + { 0x0121, 0x04 }, + { 0x0122, 0x04 }, + { 0x052e, 0x10 }, + { 0x00a4, 0xca }, + { 0x00a7, 0x40 }, + { 0x0526, 0x01 }, }; static struct { @@ -654,12 +653,12 @@ static int au8522_read_status(struct dvb_frontend *fe, enum fe_status *status) if (state->current_modulation == VSB_8) { dprintk("%s() Checking VSB_8\n", __func__); - reg = au8522_readreg(state, 0x4088); + reg = au8522_readreg(state, 0x0088); if ((reg & 0x03) == 0x03) *status |= FE_HAS_LOCK | FE_HAS_SYNC | FE_HAS_VITERBI; } else { dprintk("%s() Checking QAM\n", __func__); - reg = au8522_readreg(state, 0x4541); + reg = au8522_readreg(state, 0x0541); if (reg & 0x80) *status |= FE_HAS_VITERBI; if (reg & 0x20) @@ -745,17 +744,17 @@ static int au8522_read_snr(struct dvb_frontend *fe, u16 *snr) if (state->current_modulation == QAM_256) ret = au8522_mse2snr_lookup(qam256_mse2snr_tab, ARRAY_SIZE(qam256_mse2snr_tab), - au8522_readreg(state, 0x4522), + au8522_readreg(state, 0x0522), snr); else if (state->current_modulation == QAM_64) ret = au8522_mse2snr_lookup(qam64_mse2snr_tab, ARRAY_SIZE(qam64_mse2snr_tab), - au8522_readreg(state, 0x4522), + au8522_readreg(state, 0x0522), snr); else /* VSB_8 */ ret = au8522_mse2snr_lookup(vsb_mse2snr_tab, ARRAY_SIZE(vsb_mse2snr_tab), - au8522_readreg(state, 0x4311), + au8522_readreg(state, 0x0311), snr); if (state->config.led_cfg) @@ -804,9 +803,9 @@ static int au8522_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) struct au8522_state *state = fe->demodulator_priv; if (state->current_modulation == VSB_8) - *ucblocks = au8522_readreg(state, 0x4087); + *ucblocks = au8522_readreg(state, 0x0087); else - *ucblocks = au8522_readreg(state, 0x4543); + *ucblocks = au8522_readreg(state, 0x0543); return 0; } diff --git a/drivers/media/dvb-frontends/bcm3510.c b/drivers/media/dvb-frontends/bcm3510.c index 617c5e29f919..ba63ad170d3c 100644 --- a/drivers/media/dvb-frontends/bcm3510.c +++ b/drivers/media/dvb-frontends/bcm3510.c @@ -538,6 +538,7 @@ static int bcm3510_set_frontend(struct dvb_frontend *fe) cmd.ACQUIRE0.MODE = 0x9; cmd.ACQUIRE1.SYM_RATE = 0x0; cmd.ACQUIRE1.IF_FREQ = 0x0; + break; default: return -EINVAL; } @@ -772,7 +773,8 @@ static int bcm3510_init(struct dvb_frontend* fe) deb_info("attempting to download firmware\n"); if ((ret = bcm3510_init_cold(st)) < 0) return ret; - case JDEC_EEPROM_LOAD_WAIT: /* fall-through is wanted */ + /* fall-through */ + case JDEC_EEPROM_LOAD_WAIT: deb_info("firmware is loaded\n"); bcm3510_check_firmware_version(st); break; diff --git a/drivers/media/dvb-frontends/dib7000p.c b/drivers/media/dvb-frontends/dib7000p.c index 3815ea515364..1caa04d8f60f 100644 --- a/drivers/media/dvb-frontends/dib7000p.c +++ b/drivers/media/dvb-frontends/dib7000p.c @@ -279,10 +279,10 @@ static int dib7000p_set_power_mode(struct dib7000p_state *state, enum dib7000p_p if (state->version != SOC7090) reg_1280 &= ~((1 << 11)); reg_1280 &= ~(1 << 6); - /* fall through wanted to enable the interfaces */ - + /* fall-through */ + case DIB7000P_POWER_INTERFACE_ONLY: /* just leave power on the control-interfaces: GPIO and (I2C or SDIO) */ - case DIB7000P_POWER_INTERFACE_ONLY: /* TODO power up either SDIO or I2C */ + /* TODO power up either SDIO or I2C */ if (state->version == SOC7090) reg_1280 &= ~((1 << 7) | (1 << 5)); else diff --git a/drivers/media/dvb-frontends/drx39xyj/drxj.c b/drivers/media/dvb-frontends/drx39xyj/drxj.c index daeaf965dd56..14040c915dbb 100644 --- a/drivers/media/dvb-frontends/drx39xyj/drxj.c +++ b/drivers/media/dvb-frontends/drx39xyj/drxj.c @@ -2837,7 +2837,8 @@ ctrl_set_cfg_mpeg_output(struct drx_demod_instance *demod, struct drx_cfg_mpeg_o /* coef = 188/204 */ max_bit_rate = (ext_attr->curr_symbol_rate / 8) * nr_bits * 188; - /* pass through b/c Annex A/c need following settings */ + /* pass through as b/c Annex A/c need following settings */ + /* fall-through */ case DRX_STANDARD_ITU_B: rc = drxj_dap_write_reg16(dev_addr, FEC_OC_FCT_USAGE__A, FEC_OC_FCT_USAGE__PRE, 0); if (rc != 0) { @@ -4776,9 +4777,9 @@ set_frequency(struct drx_demod_instance *demod, No need to account for mirroring on RF */ switch (ext_attr->standard) { - case DRX_STANDARD_ITU_A: /* fallthrough */ - case DRX_STANDARD_ITU_C: /* fallthrough */ - case DRX_STANDARD_PAL_SECAM_LP: /* fallthrough */ + case DRX_STANDARD_ITU_A: + case DRX_STANDARD_ITU_C: + case DRX_STANDARD_PAL_SECAM_LP: case DRX_STANDARD_8VSB: select_pos_image = true; break; @@ -4787,11 +4788,12 @@ set_frequency(struct drx_demod_instance *demod, Sound carrier is already 3Mhz above centre frequency due to tuner setting so now add an extra shift of 1MHz... */ fm_frequency_shift = 1000; - case DRX_STANDARD_ITU_B: /* fallthrough */ - case DRX_STANDARD_NTSC: /* fallthrough */ - case DRX_STANDARD_PAL_SECAM_BG: /* fallthrough */ - case DRX_STANDARD_PAL_SECAM_DK: /* fallthrough */ - case DRX_STANDARD_PAL_SECAM_I: /* fallthrough */ + /*fall through */ + case DRX_STANDARD_ITU_B: + case DRX_STANDARD_NTSC: + case DRX_STANDARD_PAL_SECAM_BG: + case DRX_STANDARD_PAL_SECAM_DK: + case DRX_STANDARD_PAL_SECAM_I: case DRX_STANDARD_PAL_SECAM_L: select_pos_image = false; break; diff --git a/drivers/media/dvb-frontends/drxd_hard.c b/drivers/media/dvb-frontends/drxd_hard.c index 71910561005f..17638e08835a 100644 --- a/drivers/media/dvb-frontends/drxd_hard.c +++ b/drivers/media/dvb-frontends/drxd_hard.c @@ -1517,12 +1517,14 @@ static int SetDeviceTypeId(struct drxd_state *state) switch (deviceId) { case 4: state->diversity = 1; + /* fall through */ case 3: case 7: state->PGA = 1; break; case 6: state->diversity = 1; + /* fall through */ case 5: case 8: break; @@ -1969,7 +1971,8 @@ static int DRX_Start(struct drxd_state *state, s32 off) switch (p->transmission_mode) { default: /* Not set, detect it automatically */ operationMode |= SC_RA_RAM_OP_AUTO_MODE__M; - /* fall through , try first guess DRX_FFTMODE_8K */ + /* try first guess DRX_FFTMODE_8K */ + /* fall through */ case TRANSMISSION_MODE_8K: transmissionParams |= SC_RA_RAM_OP_PARAM_MODE_8K; if (state->type_A) { @@ -2143,8 +2146,8 @@ static int DRX_Start(struct drxd_state *state, s32 off) switch (p->modulation) { default: operationMode |= SC_RA_RAM_OP_AUTO_CONST__M; - /* fall through , try first guess - DRX_CONSTELLATION_QAM64 */ + /* try first guess DRX_CONSTELLATION_QAM64 */ + /* fall through */ case QAM_64: transmissionParams |= SC_RA_RAM_OP_PARAM_CONST_QAM64; if (state->type_A) { @@ -2280,6 +2283,7 @@ static int DRX_Start(struct drxd_state *state, s32 off) break; default: operationMode |= SC_RA_RAM_OP_AUTO_RATE__M; + /* fall through */ case FEC_2_3: transmissionParams |= SC_RA_RAM_OP_PARAM_RATE_2_3; if (state->type_A) { diff --git a/drivers/media/dvb-frontends/drxk_hard.c b/drivers/media/dvb-frontends/drxk_hard.c index 050fe34342d3..48a8aad47a74 100644 --- a/drivers/media/dvb-frontends/drxk_hard.c +++ b/drivers/media/dvb-frontends/drxk_hard.c @@ -3271,10 +3271,12 @@ static int dvbt_sc_command(struct drxk_state *state, case OFDM_SC_RA_RAM_CMD_PROGRAM_PARAM: status |= write16(state, OFDM_SC_RA_RAM_PARAM1__A, param1); /* All commands using 1 parameters */ + /* fall through */ case OFDM_SC_RA_RAM_CMD_SET_ECHO_TIMING: case OFDM_SC_RA_RAM_CMD_USER_IO: status |= write16(state, OFDM_SC_RA_RAM_PARAM0__A, param0); /* All commands using 0 parameters */ + /* fall through */ case OFDM_SC_RA_RAM_CMD_GET_OP_PARAM: case OFDM_SC_RA_RAM_CMD_NULL: /* Write command */ @@ -3782,7 +3784,8 @@ static int set_dvbt(struct drxk_state *state, u16 intermediate_freqk_hz, case TRANSMISSION_MODE_AUTO: default: operation_mode |= OFDM_SC_RA_RAM_OP_AUTO_MODE__M; - /* fall through , try first guess DRX_FFTMODE_8K */ + /* try first guess DRX_FFTMODE_8K */ + /* fall through */ case TRANSMISSION_MODE_8K: transmission_params |= OFDM_SC_RA_RAM_OP_PARAM_MODE_8K; break; @@ -3796,7 +3799,8 @@ static int set_dvbt(struct drxk_state *state, u16 intermediate_freqk_hz, default: case GUARD_INTERVAL_AUTO: operation_mode |= OFDM_SC_RA_RAM_OP_AUTO_GUARD__M; - /* fall through , try first guess DRX_GUARD_1DIV4 */ + /* try first guess DRX_GUARD_1DIV4 */ + /* fall through */ case GUARD_INTERVAL_1_4: transmission_params |= OFDM_SC_RA_RAM_OP_PARAM_GUARD_4; break; @@ -3817,9 +3821,9 @@ static int set_dvbt(struct drxk_state *state, u16 intermediate_freqk_hz, case HIERARCHY_NONE: default: operation_mode |= OFDM_SC_RA_RAM_OP_AUTO_HIER__M; - /* fall through , try first guess SC_RA_RAM_OP_PARAM_HIER_NO */ + /* try first guess SC_RA_RAM_OP_PARAM_HIER_NO */ /* transmission_params |= OFDM_SC_RA_RAM_OP_PARAM_HIER_NO; */ - /* break; */ + /* fall through */ case HIERARCHY_1: transmission_params |= OFDM_SC_RA_RAM_OP_PARAM_HIER_A1; break; @@ -3837,7 +3841,8 @@ static int set_dvbt(struct drxk_state *state, u16 intermediate_freqk_hz, case QAM_AUTO: default: operation_mode |= OFDM_SC_RA_RAM_OP_AUTO_CONST__M; - /* fall through , try first guess DRX_CONSTELLATION_QAM64 */ + /* try first guess DRX_CONSTELLATION_QAM64 */ + /* fall through */ case QAM_64: transmission_params |= OFDM_SC_RA_RAM_OP_PARAM_CONST_QAM64; break; @@ -3880,7 +3885,8 @@ static int set_dvbt(struct drxk_state *state, u16 intermediate_freqk_hz, case FEC_AUTO: default: operation_mode |= OFDM_SC_RA_RAM_OP_AUTO_RATE__M; - /* fall through , try first guess DRX_CODERATE_2DIV3 */ + /* try first guess DRX_CODERATE_2DIV3 */ + /* fall through */ case FEC_2_3: transmission_params |= OFDM_SC_RA_RAM_OP_PARAM_RATE_2_3; break; @@ -3914,7 +3920,7 @@ static int set_dvbt(struct drxk_state *state, u16 intermediate_freqk_hz, switch (state->props.bandwidth_hz) { case 0: state->props.bandwidth_hz = 8000000; - /* fall though */ + /* fall through */ case 8000000: bandwidth = DRXK_BANDWIDTH_8MHZ_IN_HZ; status = write16(state, OFDM_SC_RA_RAM_SRMM_FIX_FACT_8K__A, diff --git a/drivers/media/dvb-frontends/mt352.c b/drivers/media/dvb-frontends/mt352.c index e127090f2d22..d5fa96f0a6cd 100644 --- a/drivers/media/dvb-frontends/mt352.c +++ b/drivers/media/dvb-frontends/mt352.c @@ -211,6 +211,7 @@ static int mt352_set_parameters(struct dvb_frontend *fe) if (op->hierarchy == HIERARCHY_AUTO || op->hierarchy == HIERARCHY_NONE) break; + /* fall through */ default: return -EINVAL; } diff --git a/drivers/media/dvb-frontends/or51132.c b/drivers/media/dvb-frontends/or51132.c index 62aa00767015..5f2549c48eb0 100644 --- a/drivers/media/dvb-frontends/or51132.c +++ b/drivers/media/dvb-frontends/or51132.c @@ -493,8 +493,8 @@ start: switch (reg&0xff) { case 0x06: if (reg & 0x1000) usK = 3 << 24; - /* Fall through to QAM64 case */ - case 0x43: + /* fall through */ + case 0x43: /* QAM64 */ c = 150204167; break; case 0x45: diff --git a/drivers/media/dvb-frontends/s5h1411.c b/drivers/media/dvb-frontends/s5h1411.c index f29750a96196..dd09336a135b 100644 --- a/drivers/media/dvb-frontends/s5h1411.c +++ b/drivers/media/dvb-frontends/s5h1411.c @@ -51,7 +51,7 @@ static int debug; #define dprintk(arg...) do { \ if (debug) \ printk(arg); \ - } while (0) +} while (0) /* Register values to initialise the demod, defaults to VSB */ static struct init_tab { @@ -410,7 +410,7 @@ static int s5h1411_set_if_freq(struct dvb_frontend *fe, int KHz) default: dprintk("%s(%d KHz) Invalid, defaulting to 5380\n", __func__, KHz); - /* no break, need to continue */ + /* fall through */ case 5380: case 44000: s5h1411_writereg(state, S5H1411_I2C_TOP_ADDR, 0x38, 0x1be4); diff --git a/drivers/media/dvb-frontends/zl10353.c b/drivers/media/dvb-frontends/zl10353.c index 47c0549eb7b2..1c689f7f4ab8 100644 --- a/drivers/media/dvb-frontends/zl10353.c +++ b/drivers/media/dvb-frontends/zl10353.c @@ -211,7 +211,7 @@ static int zl10353_set_parameters(struct dvb_frontend *fe) break; default: c->bandwidth_hz = 8000000; - /* fall though */ + /* fall through */ case 8000000: zl10353_single_write(fe, MCLK_RATIO, 0x75); zl10353_single_write(fe, 0x64, 0x36); @@ -268,6 +268,7 @@ static int zl10353_set_parameters(struct dvb_frontend *fe) if (c->hierarchy == HIERARCHY_AUTO || c->hierarchy == HIERARCHY_NONE) break; + /* fall through */ default: return -EINVAL; } diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index aaa9471c7d11..121b3b5394cb 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -209,6 +209,7 @@ config VIDEO_ADV7604 depends on VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API depends on GPIOLIB || COMPILE_TEST select HDMI + select V4L2_FWNODE ---help--- Support for the Analog Devices ADV7604 video decoder. @@ -302,6 +303,16 @@ config VIDEO_AD5820 This is a driver for the AD5820 camera lens voice coil. It is used for example in Nokia N900 (RX-51). +config VIDEO_DW9714 + tristate "DW9714 lens voice coil support" + depends on I2C && VIDEO_V4L2 && MEDIA_CONTROLLER + depends on VIDEO_V4L2_SUBDEV_API + ---help--- + This is a driver for the DW9714 camera lens voice coil. + DW9714 is a 10 bit DAC with 120mA output current sink + capability. This is designed for linear control of + voice coil motors, controlled via I2C serial interface. + config VIDEO_SAA7110 tristate "Philips SAA7110 video decoder" depends on VIDEO_V4L2 && I2C @@ -324,6 +335,7 @@ config VIDEO_TC358743 tristate "Toshiba TC358743 decoder" depends on VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API select HDMI + select V4L2_FWNODE ---help--- Support for the Toshiba TC358743 HDMI to MIPI CSI-2 bridge. @@ -333,6 +345,7 @@ config VIDEO_TC358743 config VIDEO_TVP514X tristate "Texas Instruments TVP514x video decoder" depends on VIDEO_V4L2 && I2C + select V4L2_FWNODE ---help--- This is a Video4Linux2 sensor-level driver for the TI TVP5146/47 decoder. It is currently working with the TI OMAP3 camera @@ -344,6 +357,7 @@ config VIDEO_TVP514X config VIDEO_TVP5150 tristate "Texas Instruments TVP5150 video decoder" depends on VIDEO_V4L2 && I2C + select V4L2_FWNODE ---help--- Support for the Texas Instruments TVP5150 video decoder. @@ -353,6 +367,7 @@ config VIDEO_TVP5150 config VIDEO_TVP7002 tristate "Texas Instruments TVP7002 video decoder" depends on VIDEO_V4L2 && I2C + select V4L2_FWNODE ---help--- Support for the Texas Instruments TVP7002 video decoder. @@ -535,6 +550,7 @@ config VIDEO_OV2659 tristate "OmniVision OV2659 sensor support" depends on VIDEO_V4L2 && I2C depends on MEDIA_CAMERA_SUPPORT + select V4L2_FWNODE ---help--- This is a Video4Linux2 sensor-level driver for the OmniVision OV2659 camera. @@ -542,11 +558,22 @@ config VIDEO_OV2659 To compile this driver as a module, choose M here: the module will be called ov2659. +config VIDEO_OV5640 + tristate "OmniVision OV5640 sensor support" + depends on OF + depends on GPIOLIB && VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API + depends on MEDIA_CAMERA_SUPPORT + select V4L2_FWNODE + ---help--- + This is a Video4Linux2 sensor-level driver for the Omnivision + OV5640 camera sensor with a MIPI CSI-2 interface. + config VIDEO_OV5645 tristate "OmniVision OV5645 sensor support" depends on OF depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API depends on MEDIA_CAMERA_SUPPORT + select V4L2_FWNODE ---help--- This is a Video4Linux2 sensor-level driver for the OmniVision OV5645 camera. @@ -558,6 +585,7 @@ config VIDEO_OV5647 tristate "OmniVision OV5647 sensor support" depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API depends on MEDIA_CAMERA_SUPPORT + select V4L2_FWNODE ---help--- This is a Video4Linux2 sensor-level driver for the OmniVision OV5647 camera. @@ -592,6 +620,14 @@ config VIDEO_OV9650 This is a V4L2 sensor-level driver for the Omnivision OV9650 and OV9652 camera sensors. +config VIDEO_OV13858 + tristate "OmniVision OV13858 sensor support" + depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API + depends on MEDIA_CAMERA_SUPPORT + ---help--- + This is a Video4Linux2 sensor-level driver for the OmniVision + OV13858 camera. + config VIDEO_VS6624 tristate "ST VS6624 sensor support" depends on VIDEO_V4L2 && I2C @@ -650,6 +686,7 @@ config VIDEO_MT9V032 depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API depends on MEDIA_CAMERA_SUPPORT select REGMAP_I2C + select V4L2_FWNODE ---help--- This is a Video4Linux2 sensor-level driver for the Micron MT9V032 752x480 CMOS sensor. @@ -697,6 +734,7 @@ config VIDEO_S5K4ECGX config VIDEO_S5K5BAF tristate "Samsung S5K5BAF sensor support" depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE ---help--- This is a V4L2 sensor-level driver for Samsung S5K5BAF 2M camera sensor with an embedded SoC image signal processor. @@ -707,6 +745,7 @@ source "drivers/media/i2c/et8ek8/Kconfig" config VIDEO_S5C73M3 tristate "Samsung S5C73M3 sensor support" depends on I2C && SPI && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE ---help--- This is a V4L2 sensor-level driver for Samsung S5C73M3 8 Mpixel camera. @@ -785,6 +824,18 @@ config VIDEO_SAA6752HS To compile this driver as a module, choose M here: the module will be called saa6752hs. +comment "SDR tuner chips" + +config SDR_MAX2175 + tristate "Maxim 2175 RF to Bits tuner" + depends on VIDEO_V4L2 && MEDIA_SDR_SUPPORT && I2C + ---help--- + Support for Maxim 2175 tuner. It is an advanced analog/digital + radio receiver with RF-to-Bits front-end designed for SDR solutions. + + To compile this driver as a module, choose M here; the + module will be called max2175. + comment "Miscellaneous helper chips" config VIDEO_THS7303 diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 62323ec66be8..2c0868fa6034 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -21,6 +21,7 @@ obj-$(CONFIG_VIDEO_SAA7127) += saa7127.o obj-$(CONFIG_VIDEO_SAA7185) += saa7185.o obj-$(CONFIG_VIDEO_SAA6752HS) += saa6752hs.o obj-$(CONFIG_VIDEO_AD5820) += ad5820.o +obj-$(CONFIG_VIDEO_DW9714) += dw9714.o obj-$(CONFIG_VIDEO_ADV7170) += adv7170.o obj-$(CONFIG_VIDEO_ADV7175) += adv7175.o obj-$(CONFIG_VIDEO_ADV7180) += adv7180.o @@ -58,11 +59,13 @@ obj-$(CONFIG_VIDEO_SONY_BTF_MPX) += sony-btf-mpx.o obj-$(CONFIG_VIDEO_UPD64031A) += upd64031a.o obj-$(CONFIG_VIDEO_UPD64083) += upd64083.o obj-$(CONFIG_VIDEO_OV2640) += ov2640.o +obj-$(CONFIG_VIDEO_OV5640) += ov5640.o obj-$(CONFIG_VIDEO_OV5645) += ov5645.o obj-$(CONFIG_VIDEO_OV5647) += ov5647.o obj-$(CONFIG_VIDEO_OV7640) += ov7640.o obj-$(CONFIG_VIDEO_OV7670) += ov7670.o obj-$(CONFIG_VIDEO_OV9650) += ov9650.o +obj-$(CONFIG_VIDEO_OV13858) += ov13858.o obj-$(CONFIG_VIDEO_MT9M032) += mt9m032.o obj-$(CONFIG_VIDEO_MT9M111) += mt9m111.o obj-$(CONFIG_VIDEO_MT9P031) += mt9p031.o @@ -86,3 +89,5 @@ obj-$(CONFIG_VIDEO_IR_I2C) += ir-kbd-i2c.o obj-$(CONFIG_VIDEO_ML86V7667) += ml86v7667.o obj-$(CONFIG_VIDEO_OV2659) += ov2659.o obj-$(CONFIG_VIDEO_TC358743) += tc358743.o + +obj-$(CONFIG_SDR_MAX2175) += max2175.o diff --git a/drivers/media/i2c/ad5820.c b/drivers/media/i2c/ad5820.c index 3d2a3c6b67d8..034ebf754007 100644 --- a/drivers/media/i2c/ad5820.c +++ b/drivers/media/i2c/ad5820.c @@ -341,7 +341,7 @@ static int ad5820_remove(struct i2c_client *client) struct v4l2_subdev *subdev = i2c_get_clientdata(client); struct ad5820_device *coil = to_ad5820_device(subdev); - v4l2_device_unregister_subdev(&coil->subdev); + v4l2_async_unregister_subdev(&coil->subdev); v4l2_ctrl_handler_free(&coil->ctrls); media_entity_cleanup(&coil->subdev.entity); mutex_destroy(&coil->power_lock); diff --git a/drivers/media/i2c/adv7180.c b/drivers/media/i2c/adv7180.c index bdbbf8cf27e4..78de7ddf5081 100644 --- a/drivers/media/i2c/adv7180.c +++ b/drivers/media/i2c/adv7180.c @@ -1452,6 +1452,8 @@ static SIMPLE_DEV_PM_OPS(adv7180_pm_ops, adv7180_suspend, adv7180_resume); #ifdef CONFIG_OF static const struct of_device_id adv7180_of_id[] = { { .compatible = "adi,adv7180", }, + { .compatible = "adi,adv7180cp", }, + { .compatible = "adi,adv7180st", }, { .compatible = "adi,adv7182", }, { .compatible = "adi,adv7280", }, { .compatible = "adi,adv7280-m", }, diff --git a/drivers/media/i2c/adv7604.c b/drivers/media/i2c/adv7604.c index f1fa9cec489f..660bacb8f7d9 100644 --- a/drivers/media/i2c/adv7604.c +++ b/drivers/media/i2c/adv7604.c @@ -33,6 +33,7 @@ #include <linux/i2c.h> #include <linux/kernel.h> #include <linux/module.h> +#include <linux/of_graph.h> #include <linux/slab.h> #include <linux/v4l2-dv-timings.h> #include <linux/videodev2.h> @@ -45,7 +46,7 @@ #include <media/v4l2-device.h> #include <media/v4l2-event.h> #include <media/v4l2-dv-timings.h> -#include <media/v4l2-of.h> +#include <media/v4l2-fwnode.h> static int debug; module_param(debug, int, 0644); @@ -3069,7 +3070,7 @@ MODULE_DEVICE_TABLE(of, adv76xx_of_id); static int adv76xx_parse_dt(struct adv76xx_state *state) { - struct v4l2_of_endpoint bus_cfg; + struct v4l2_fwnode_endpoint bus_cfg; struct device_node *endpoint; struct device_node *np; unsigned int flags; @@ -3083,7 +3084,7 @@ static int adv76xx_parse_dt(struct adv76xx_state *state) if (!endpoint) return -EINVAL; - ret = v4l2_of_parse_endpoint(endpoint, &bus_cfg); + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(endpoint), &bus_cfg); if (ret) { of_node_put(endpoint); return ret; diff --git a/drivers/media/i2c/as3645a.c b/drivers/media/i2c/as3645a.c index b6aeceea9850..af5db71a0888 100644 --- a/drivers/media/i2c/as3645a.c +++ b/drivers/media/i2c/as3645a.c @@ -294,8 +294,8 @@ static int as3645a_read_fault(struct as3645a *flash) dev_dbg(&client->dev, "Inductor Peak limit fault\n"); if (rval & AS_FAULT_INFO_INDICATOR_LED) - dev_dbg(&client->dev, "Indicator LED fault: " - "Short circuit or open loop\n"); + dev_dbg(&client->dev, + "Indicator LED fault: Short circuit or open loop\n"); dev_dbg(&client->dev, "%u connected LEDs\n", rval & AS_FAULT_INFO_LED_AMOUNT ? 2 : 1); @@ -310,8 +310,8 @@ static int as3645a_read_fault(struct as3645a *flash) dev_dbg(&client->dev, "Short circuit fault\n"); if (rval & AS_FAULT_INFO_OVER_VOLTAGE) - dev_dbg(&client->dev, "Over voltage fault: " - "Indicates missing capacitor or open connection\n"); + dev_dbg(&client->dev, + "Over voltage fault: Indicates missing capacitor or open connection\n"); return rval; } @@ -583,8 +583,8 @@ static int as3645a_registered(struct v4l2_subdev *sd) /* Verify the chip model and version. */ if (model != 0x01 || rfu != 0x00) { - dev_err(&client->dev, "AS3645A not detected " - "(model %d rfu %d)\n", model, rfu); + dev_err(&client->dev, + "AS3645A not detected (model %d rfu %d)\n", model, rfu); rval = -ENODEV; goto power_off; } diff --git a/drivers/media/i2c/cx25840/cx25840-core.c b/drivers/media/i2c/cx25840/cx25840-core.c index b8d3c070bfc1..39f51daa7558 100644 --- a/drivers/media/i2c/cx25840/cx25840-core.c +++ b/drivers/media/i2c/cx25840/cx25840-core.c @@ -416,11 +416,13 @@ static void cx25840_initialize(struct i2c_client *client) INIT_WORK(&state->fw_work, cx25840_work_handler); init_waitqueue_head(&state->fw_wait); q = create_singlethread_workqueue("cx25840_fw"); - prepare_to_wait(&state->fw_wait, &wait, TASK_UNINTERRUPTIBLE); - queue_work(q, &state->fw_work); - schedule(); - finish_wait(&state->fw_wait, &wait); - destroy_workqueue(q); + if (q) { + prepare_to_wait(&state->fw_wait, &wait, TASK_UNINTERRUPTIBLE); + queue_work(q, &state->fw_work); + schedule(); + finish_wait(&state->fw_wait, &wait); + destroy_workqueue(q); + } /* 6. */ cx25840_write(client, 0x115, 0x8c); @@ -630,11 +632,13 @@ static void cx23885_initialize(struct i2c_client *client) INIT_WORK(&state->fw_work, cx25840_work_handler); init_waitqueue_head(&state->fw_wait); q = create_singlethread_workqueue("cx25840_fw"); - prepare_to_wait(&state->fw_wait, &wait, TASK_UNINTERRUPTIBLE); - queue_work(q, &state->fw_work); - schedule(); - finish_wait(&state->fw_wait, &wait); - destroy_workqueue(q); + if (q) { + prepare_to_wait(&state->fw_wait, &wait, TASK_UNINTERRUPTIBLE); + queue_work(q, &state->fw_work); + schedule(); + finish_wait(&state->fw_wait, &wait); + destroy_workqueue(q); + } /* Call the cx23888 specific std setup func, we no longer rely on * the generic cx24840 func. @@ -748,11 +752,13 @@ static void cx231xx_initialize(struct i2c_client *client) INIT_WORK(&state->fw_work, cx25840_work_handler); init_waitqueue_head(&state->fw_wait); q = create_singlethread_workqueue("cx25840_fw"); - prepare_to_wait(&state->fw_wait, &wait, TASK_UNINTERRUPTIBLE); - queue_work(q, &state->fw_work); - schedule(); - finish_wait(&state->fw_wait, &wait); - destroy_workqueue(q); + if (q) { + prepare_to_wait(&state->fw_wait, &wait, TASK_UNINTERRUPTIBLE); + queue_work(q, &state->fw_work); + schedule(); + finish_wait(&state->fw_wait, &wait); + destroy_workqueue(q); + } cx25840_std_setup(client); diff --git a/drivers/media/i2c/dw9714.c b/drivers/media/i2c/dw9714.c new file mode 100644 index 000000000000..6a607d7f82de --- /dev/null +++ b/drivers/media/i2c/dw9714.c @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2015--2017 Intel Corporation. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * 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. + */ + +#include <linux/acpi.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> + +#define DW9714_NAME "dw9714" +#define DW9714_MAX_FOCUS_POS 1023 +/* + * This acts as the minimum granularity of lens movement. + * Keep this value power of 2, so the control steps can be + * uniformly adjusted for gradual lens movement, with desired + * number of control steps. + */ +#define DW9714_CTRL_STEPS 16 +#define DW9714_CTRL_DELAY_US 1000 +/* + * S[3:2] = 0x00, codes per step for "Linear Slope Control" + * S[1:0] = 0x00, step period + */ +#define DW9714_DEFAULT_S 0x0 +#define DW9714_VAL(data, s) ((data) << 4 | (s)) + +/* dw9714 device structure */ +struct dw9714_device { + struct i2c_client *client; + struct v4l2_ctrl_handler ctrls_vcm; + struct v4l2_subdev sd; + u16 current_val; +}; + +static inline struct dw9714_device *to_dw9714_vcm(struct v4l2_ctrl *ctrl) +{ + return container_of(ctrl->handler, struct dw9714_device, ctrls_vcm); +} + +static inline struct dw9714_device *sd_to_dw9714_vcm(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct dw9714_device, sd); +} + +static int dw9714_i2c_write(struct i2c_client *client, u16 data) +{ + int ret; + u16 val = cpu_to_be16(data); + + ret = i2c_master_send(client, (const char *)&val, sizeof(val)); + if (ret != sizeof(val)) { + dev_err(&client->dev, "I2C write fail\n"); + return -EIO; + } + return 0; +} + +static int dw9714_t_focus_vcm(struct dw9714_device *dw9714_dev, u16 val) +{ + struct i2c_client *client = dw9714_dev->client; + + dw9714_dev->current_val = val; + + return dw9714_i2c_write(client, DW9714_VAL(val, DW9714_DEFAULT_S)); +} + +static int dw9714_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct dw9714_device *dev_vcm = to_dw9714_vcm(ctrl); + + if (ctrl->id == V4L2_CID_FOCUS_ABSOLUTE) + return dw9714_t_focus_vcm(dev_vcm, ctrl->val); + + return -EINVAL; +} + +static const struct v4l2_ctrl_ops dw9714_vcm_ctrl_ops = { + .s_ctrl = dw9714_set_ctrl, +}; + +static int dw9714_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct dw9714_device *dw9714_dev = sd_to_dw9714_vcm(sd); + struct device *dev = &dw9714_dev->client->dev; + int rval; + + rval = pm_runtime_get_sync(dev); + if (rval < 0) { + pm_runtime_put_noidle(dev); + return rval; + } + + return 0; +} + +static int dw9714_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct dw9714_device *dw9714_dev = sd_to_dw9714_vcm(sd); + struct device *dev = &dw9714_dev->client->dev; + + pm_runtime_put(dev); + + return 0; +} + +static const struct v4l2_subdev_internal_ops dw9714_int_ops = { + .open = dw9714_open, + .close = dw9714_close, +}; + +static const struct v4l2_subdev_ops dw9714_ops = { }; + +static void dw9714_subdev_cleanup(struct dw9714_device *dw9714_dev) +{ + v4l2_async_unregister_subdev(&dw9714_dev->sd); + v4l2_ctrl_handler_free(&dw9714_dev->ctrls_vcm); + media_entity_cleanup(&dw9714_dev->sd.entity); +} + +static int dw9714_init_controls(struct dw9714_device *dev_vcm) +{ + struct v4l2_ctrl_handler *hdl = &dev_vcm->ctrls_vcm; + const struct v4l2_ctrl_ops *ops = &dw9714_vcm_ctrl_ops; + struct i2c_client *client = dev_vcm->client; + + v4l2_ctrl_handler_init(hdl, 1); + + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FOCUS_ABSOLUTE, + 0, DW9714_MAX_FOCUS_POS, DW9714_CTRL_STEPS, 0); + + if (hdl->error) + dev_err(&client->dev, "%s fail error: 0x%x\n", + __func__, hdl->error); + dev_vcm->sd.ctrl_handler = hdl; + return hdl->error; +} + +static int dw9714_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct dw9714_device *dw9714_dev; + int rval; + + dw9714_dev = devm_kzalloc(&client->dev, sizeof(*dw9714_dev), + GFP_KERNEL); + if (dw9714_dev == NULL) + return -ENOMEM; + + dw9714_dev->client = client; + + v4l2_i2c_subdev_init(&dw9714_dev->sd, client, &dw9714_ops); + dw9714_dev->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + dw9714_dev->sd.internal_ops = &dw9714_int_ops; + + rval = dw9714_init_controls(dw9714_dev); + if (rval) + goto err_cleanup; + + rval = media_entity_pads_init(&dw9714_dev->sd.entity, 0, NULL); + if (rval < 0) + goto err_cleanup; + + dw9714_dev->sd.entity.function = MEDIA_ENT_F_LENS; + + rval = v4l2_async_register_subdev(&dw9714_dev->sd); + if (rval < 0) + goto err_cleanup; + + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + + return 0; + +err_cleanup: + dw9714_subdev_cleanup(dw9714_dev); + dev_err(&client->dev, "Probe failed: %d\n", rval); + return rval; +} + +static int dw9714_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct dw9714_device *dw9714_dev = sd_to_dw9714_vcm(sd); + + pm_runtime_disable(&client->dev); + dw9714_subdev_cleanup(dw9714_dev); + + return 0; +} + +/* + * This function sets the vcm position, so it consumes least current + * The lens position is gradually moved in units of DW9714_CTRL_STEPS, + * to make the movements smoothly. + */ +static int __maybe_unused dw9714_vcm_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct dw9714_device *dw9714_dev = sd_to_dw9714_vcm(sd); + int ret, val; + + for (val = dw9714_dev->current_val & ~(DW9714_CTRL_STEPS - 1); + val >= 0; val -= DW9714_CTRL_STEPS) { + ret = dw9714_i2c_write(client, + DW9714_VAL(val, DW9714_DEFAULT_S)); + if (ret) + dev_err_once(dev, "%s I2C failure: %d", __func__, ret); + usleep_range(DW9714_CTRL_DELAY_US, DW9714_CTRL_DELAY_US + 10); + } + return 0; +} + +/* + * This function sets the vcm position to the value set by the user + * through v4l2_ctrl_ops s_ctrl handler + * The lens position is gradually moved in units of DW9714_CTRL_STEPS, + * to make the movements smoothly. + */ +static int __maybe_unused dw9714_vcm_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct dw9714_device *dw9714_dev = sd_to_dw9714_vcm(sd); + int ret, val; + + for (val = dw9714_dev->current_val % DW9714_CTRL_STEPS; + val < dw9714_dev->current_val + DW9714_CTRL_STEPS - 1; + val += DW9714_CTRL_STEPS) { + ret = dw9714_i2c_write(client, + DW9714_VAL(val, DW9714_DEFAULT_S)); + if (ret) + dev_err_ratelimited(dev, "%s I2C failure: %d", + __func__, ret); + usleep_range(DW9714_CTRL_DELAY_US, DW9714_CTRL_DELAY_US + 10); + } + + return 0; +} + +#ifdef CONFIG_ACPI +static const struct acpi_device_id dw9714_acpi_match[] = { + {}, +}; +MODULE_DEVICE_TABLE(acpi, dw9714_acpi_match); +#endif + +static const struct i2c_device_id dw9714_id_table[] = { + {DW9714_NAME, 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, dw9714_id_table); + +static const struct dev_pm_ops dw9714_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(dw9714_vcm_suspend, dw9714_vcm_resume) + SET_RUNTIME_PM_OPS(dw9714_vcm_suspend, dw9714_vcm_resume, NULL) +}; + +static struct i2c_driver dw9714_i2c_driver = { + .driver = { + .name = DW9714_NAME, + .pm = &dw9714_pm_ops, + .acpi_match_table = ACPI_PTR(dw9714_acpi_match), + }, + .probe = dw9714_probe, + .remove = dw9714_remove, + .id_table = dw9714_id_table, +}; + +module_i2c_driver(dw9714_i2c_driver); + +MODULE_AUTHOR("Tianshu Qiu <tian.shu.qiu@intel.com>"); +MODULE_AUTHOR("Jian Xu Zheng <jian.xu.zheng@intel.com>"); +MODULE_AUTHOR("Yuning Pu <yuning.pu@intel.com>"); +MODULE_AUTHOR("Jouni Ukkonen <jouni.ukkonen@intel.com>"); +MODULE_AUTHOR("Tommi Franttila <tommi.franttila@intel.com>"); +MODULE_DESCRIPTION("DW9714 VCM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/max2175.c b/drivers/media/i2c/max2175.c new file mode 100644 index 000000000000..a4736a8a7792 --- /dev/null +++ b/drivers/media/i2c/max2175.c @@ -0,0 +1,1453 @@ +/* + * Maxim Integrated MAX2175 RF to Bits tuner driver + * + * This driver & most of the hard coded values are based on the reference + * application delivered by Maxim for this device. + * + * Copyright (C) 2016 Maxim Integrated Products + * Copyright (C) 2017 Renesas Electronics Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/math64.h> +#include <linux/max2175.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> + +#include "max2175.h" + +#define DRIVER_NAME "max2175" + +#define mxm_dbg(ctx, fmt, arg...) dev_dbg(&ctx->client->dev, fmt, ## arg) +#define mxm_err(ctx, fmt, arg...) dev_err(&ctx->client->dev, fmt, ## arg) + +/* Rx mode */ +struct max2175_rxmode { + enum max2175_band band; /* Associated band */ + u32 freq; /* Default freq in Hz */ + u8 i2s_word_size; /* Bit value */ +}; + +/* Register map to define preset values */ +struct max2175_reg_map { + u8 idx; /* Register index */ + u8 val; /* Register value */ +}; + +static const struct max2175_rxmode eu_rx_modes[] = { + /* EU modes */ + [MAX2175_EU_FM_1_2] = { MAX2175_BAND_FM, 98256000, 1 }, + [MAX2175_DAB_1_2] = { MAX2175_BAND_VHF, 182640000, 0 }, +}; + +static const struct max2175_rxmode na_rx_modes[] = { + /* NA modes */ + [MAX2175_NA_FM_1_0] = { MAX2175_BAND_FM, 98255520, 1 }, + [MAX2175_NA_FM_2_0] = { MAX2175_BAND_FM, 98255520, 6 }, +}; + +/* + * Preset values: + * Based on Maxim MAX2175 Register Table revision: 130p10 + */ +static const u8 full_fm_eu_1p0[] = { + 0x15, 0x04, 0xb8, 0xe3, 0x35, 0x18, 0x7c, 0x00, + 0x00, 0x7d, 0x40, 0x08, 0x70, 0x7a, 0x88, 0x91, + 0x61, 0x61, 0x61, 0x61, 0x5a, 0x0f, 0x34, 0x1c, + 0x14, 0x88, 0x33, 0x02, 0x00, 0x09, 0x00, 0x65, + 0x9f, 0x2b, 0x80, 0x00, 0x95, 0x05, 0x2c, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x4a, 0x08, 0xa8, 0x0e, 0x0e, 0x2f, 0x7e, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xab, 0x5e, 0xa9, + 0xae, 0xbb, 0x57, 0x18, 0x3b, 0x03, 0x3b, 0x64, + 0x40, 0x60, 0x00, 0x2a, 0xbf, 0x3f, 0xff, 0x9f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, + 0xff, 0xfc, 0xef, 0x1c, 0x40, 0x00, 0x00, 0x02, + 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xac, 0x40, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, + 0x00, 0x47, 0x00, 0x00, 0x11, 0x3f, 0x22, 0x00, + 0xf1, 0x00, 0x41, 0x03, 0xb0, 0x00, 0x00, 0x00, + 0x1b, +}; + +static const u8 full_fm_na_1p0[] = { + 0x13, 0x08, 0x8d, 0xc0, 0x35, 0x18, 0x7d, 0x3f, + 0x7d, 0x75, 0x40, 0x08, 0x70, 0x7a, 0x88, 0x91, + 0x61, 0x61, 0x61, 0x61, 0x5c, 0x0f, 0x34, 0x1c, + 0x14, 0x88, 0x33, 0x02, 0x00, 0x01, 0x00, 0x65, + 0x9f, 0x2b, 0x80, 0x00, 0x95, 0x05, 0x2c, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x4a, 0x08, 0xa8, 0x0e, 0x0e, 0xaf, 0x7e, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xab, 0x5e, 0xa9, + 0xae, 0xbb, 0x57, 0x18, 0x3b, 0x03, 0x3b, 0x64, + 0x40, 0x60, 0x00, 0x2a, 0xbf, 0x3f, 0xff, 0x9f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, + 0xff, 0xfc, 0xef, 0x1c, 0x40, 0x00, 0x00, 0x02, + 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xa6, 0x40, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, + 0x00, 0x35, 0x00, 0x00, 0x11, 0x3f, 0x22, 0x00, + 0xf1, 0x00, 0x41, 0x03, 0xb0, 0x00, 0x00, 0x00, + 0x1b, +}; + +/* DAB1.2 settings */ +static const struct max2175_reg_map dab12_map[] = { + { 0x01, 0x13 }, { 0x02, 0x0d }, { 0x03, 0x15 }, { 0x04, 0x55 }, + { 0x05, 0x0a }, { 0x06, 0xa0 }, { 0x07, 0x40 }, { 0x08, 0x00 }, + { 0x09, 0x00 }, { 0x0a, 0x7d }, { 0x0b, 0x4a }, { 0x0c, 0x28 }, + { 0x0e, 0x43 }, { 0x0f, 0xb5 }, { 0x10, 0x31 }, { 0x11, 0x9e }, + { 0x12, 0x68 }, { 0x13, 0x9e }, { 0x14, 0x68 }, { 0x15, 0x58 }, + { 0x16, 0x2f }, { 0x17, 0x3f }, { 0x18, 0x40 }, { 0x1a, 0x88 }, + { 0x1b, 0xaa }, { 0x1c, 0x9a }, { 0x1d, 0x00 }, { 0x1e, 0x00 }, + { 0x23, 0x80 }, { 0x24, 0x00 }, { 0x25, 0x00 }, { 0x26, 0x00 }, + { 0x27, 0x00 }, { 0x32, 0x08 }, { 0x33, 0xf8 }, { 0x36, 0x2d }, + { 0x37, 0x7e }, { 0x55, 0xaf }, { 0x56, 0x3f }, { 0x57, 0xf8 }, + { 0x58, 0x99 }, { 0x76, 0x00 }, { 0x77, 0x00 }, { 0x78, 0x02 }, + { 0x79, 0x40 }, { 0x82, 0x00 }, { 0x83, 0x00 }, { 0x85, 0x00 }, + { 0x86, 0x20 }, +}; + +/* EU FM 1.2 settings */ +static const struct max2175_reg_map fmeu1p2_map[] = { + { 0x01, 0x15 }, { 0x02, 0x04 }, { 0x03, 0xb8 }, { 0x04, 0xe3 }, + { 0x05, 0x35 }, { 0x06, 0x18 }, { 0x07, 0x7c }, { 0x08, 0x00 }, + { 0x09, 0x00 }, { 0x0a, 0x73 }, { 0x0b, 0x40 }, { 0x0c, 0x08 }, + { 0x0e, 0x7a }, { 0x0f, 0x88 }, { 0x10, 0x91 }, { 0x11, 0x61 }, + { 0x12, 0x61 }, { 0x13, 0x61 }, { 0x14, 0x61 }, { 0x15, 0x5a }, + { 0x16, 0x0f }, { 0x17, 0x34 }, { 0x18, 0x1c }, { 0x1a, 0x88 }, + { 0x1b, 0x33 }, { 0x1c, 0x02 }, { 0x1d, 0x00 }, { 0x1e, 0x01 }, + { 0x23, 0x80 }, { 0x24, 0x00 }, { 0x25, 0x95 }, { 0x26, 0x05 }, + { 0x27, 0x2c }, { 0x32, 0x08 }, { 0x33, 0xa8 }, { 0x36, 0x2f }, + { 0x37, 0x7e }, { 0x55, 0xbf }, { 0x56, 0x3f }, { 0x57, 0xff }, + { 0x58, 0x9f }, { 0x76, 0xac }, { 0x77, 0x40 }, { 0x78, 0x00 }, + { 0x79, 0x00 }, { 0x82, 0x47 }, { 0x83, 0x00 }, { 0x85, 0x11 }, + { 0x86, 0x3f }, +}; + +/* FM NA 1.0 settings */ +static const struct max2175_reg_map fmna1p0_map[] = { + { 0x01, 0x13 }, { 0x02, 0x08 }, { 0x03, 0x8d }, { 0x04, 0xc0 }, + { 0x05, 0x35 }, { 0x06, 0x18 }, { 0x07, 0x7d }, { 0x08, 0x3f }, + { 0x09, 0x7d }, { 0x0a, 0x75 }, { 0x0b, 0x40 }, { 0x0c, 0x08 }, + { 0x0e, 0x7a }, { 0x0f, 0x88 }, { 0x10, 0x91 }, { 0x11, 0x61 }, + { 0x12, 0x61 }, { 0x13, 0x61 }, { 0x14, 0x61 }, { 0x15, 0x5c }, + { 0x16, 0x0f }, { 0x17, 0x34 }, { 0x18, 0x1c }, { 0x1a, 0x88 }, + { 0x1b, 0x33 }, { 0x1c, 0x02 }, { 0x1d, 0x00 }, { 0x1e, 0x01 }, + { 0x23, 0x80 }, { 0x24, 0x00 }, { 0x25, 0x95 }, { 0x26, 0x05 }, + { 0x27, 0x2c }, { 0x32, 0x08 }, { 0x33, 0xa8 }, { 0x36, 0xaf }, + { 0x37, 0x7e }, { 0x55, 0xbf }, { 0x56, 0x3f }, { 0x57, 0xff }, + { 0x58, 0x9f }, { 0x76, 0xa6 }, { 0x77, 0x40 }, { 0x78, 0x00 }, + { 0x79, 0x00 }, { 0x82, 0x35 }, { 0x83, 0x00 }, { 0x85, 0x11 }, + { 0x86, 0x3f }, +}; + +/* FM NA 2.0 settings */ +static const struct max2175_reg_map fmna2p0_map[] = { + { 0x01, 0x13 }, { 0x02, 0x08 }, { 0x03, 0x8d }, { 0x04, 0xc0 }, + { 0x05, 0x35 }, { 0x06, 0x18 }, { 0x07, 0x7c }, { 0x08, 0x54 }, + { 0x09, 0xa7 }, { 0x0a, 0x55 }, { 0x0b, 0x42 }, { 0x0c, 0x48 }, + { 0x0e, 0x7a }, { 0x0f, 0x88 }, { 0x10, 0x91 }, { 0x11, 0x61 }, + { 0x12, 0x61 }, { 0x13, 0x61 }, { 0x14, 0x61 }, { 0x15, 0x5c }, + { 0x16, 0x0f }, { 0x17, 0x34 }, { 0x18, 0x1c }, { 0x1a, 0x88 }, + { 0x1b, 0x33 }, { 0x1c, 0x02 }, { 0x1d, 0x00 }, { 0x1e, 0x01 }, + { 0x23, 0x80 }, { 0x24, 0x00 }, { 0x25, 0x95 }, { 0x26, 0x05 }, + { 0x27, 0x2c }, { 0x32, 0x08 }, { 0x33, 0xa8 }, { 0x36, 0xaf }, + { 0x37, 0x7e }, { 0x55, 0xbf }, { 0x56, 0x3f }, { 0x57, 0xff }, + { 0x58, 0x9f }, { 0x76, 0xac }, { 0x77, 0xc0 }, { 0x78, 0x00 }, + { 0x79, 0x00 }, { 0x82, 0x6b }, { 0x83, 0x00 }, { 0x85, 0x11 }, + { 0x86, 0x3f }, +}; + +static const u16 ch_coeff_dab1[] = { + 0x001c, 0x0007, 0xffcd, 0x0056, 0xffa4, 0x0033, 0x0027, 0xff61, + 0x010e, 0xfec0, 0x0106, 0xffb8, 0xff1c, 0x023c, 0xfcb2, 0x039b, + 0xfd4e, 0x0055, 0x036a, 0xf7de, 0x0d21, 0xee72, 0x1499, 0x6a51, +}; + +static const u16 ch_coeff_fmeu[] = { + 0x0000, 0xffff, 0x0001, 0x0002, 0xfffa, 0xffff, 0x0015, 0xffec, + 0xffde, 0x0054, 0xfff9, 0xff52, 0x00b8, 0x00a2, 0xfe0a, 0x00af, + 0x02e3, 0xfc14, 0xfe89, 0x089d, 0xfa2e, 0xf30f, 0x25be, 0x4eb6, +}; + +static const u16 eq_coeff_fmeu1_ra02_m6db[] = { + 0x0040, 0xffc6, 0xfffa, 0x002c, 0x000d, 0xff90, 0x0037, 0x006e, + 0xffc0, 0xff5b, 0x006a, 0x00f0, 0xff57, 0xfe94, 0x0112, 0x0252, + 0xfe0c, 0xfc6a, 0x0385, 0x0553, 0xfa49, 0xf789, 0x0b91, 0x1a10, +}; + +static const u16 ch_coeff_fmna[] = { + 0x0001, 0x0003, 0xfffe, 0xfff4, 0x0000, 0x001f, 0x000c, 0xffbc, + 0xffd3, 0x007d, 0x0075, 0xff33, 0xff01, 0x0131, 0x01ef, 0xfe60, + 0xfc7a, 0x020e, 0x0656, 0xfd94, 0xf395, 0x02ab, 0x2857, 0x3d3f, +}; + +static const u16 eq_coeff_fmna1_ra02_m6db[] = { + 0xfff1, 0xffe1, 0xffef, 0x000e, 0x0030, 0x002f, 0xfff6, 0xffa7, + 0xff9d, 0x000a, 0x00a2, 0x00b5, 0xffea, 0xfed9, 0xfec5, 0x003d, + 0x0217, 0x021b, 0xff5a, 0xfc2b, 0xfcbd, 0x02c4, 0x0ac3, 0x0e85, +}; + +static const u8 adc_presets[2][23] = { + { + 0x83, 0x00, 0xcf, 0xb4, 0x0f, 0x2c, 0x0c, 0x49, + 0x00, 0x00, 0x00, 0x8c, 0x02, 0x02, 0x00, 0x04, + 0xec, 0x82, 0x4b, 0xcc, 0x01, 0x88, 0x0c, + }, + { + 0x83, 0x00, 0xcf, 0xb4, 0x0f, 0x2c, 0x0c, 0x49, + 0x00, 0x00, 0x00, 0x8c, 0x02, 0x20, 0x33, 0x8c, + 0x57, 0xd7, 0x59, 0xb7, 0x65, 0x0e, 0x0c, + }, +}; + +/* Tuner bands */ +static const struct v4l2_frequency_band eu_bands_rf = { + .tuner = 0, + .type = V4L2_TUNER_RF, + .index = 0, + .capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = 65000000, + .rangehigh = 240000000, +}; + +static const struct v4l2_frequency_band na_bands_rf = { + .tuner = 0, + .type = V4L2_TUNER_RF, + .index = 0, + .capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = 65000000, + .rangehigh = 108000000, +}; + +/* Regmap settings */ +static const struct regmap_range max2175_regmap_volatile_range[] = { + regmap_reg_range(0x30, 0x35), + regmap_reg_range(0x3a, 0x45), + regmap_reg_range(0x59, 0x5e), + regmap_reg_range(0x73, 0x75), +}; + +static const struct regmap_access_table max2175_volatile_regs = { + .yes_ranges = max2175_regmap_volatile_range, + .n_yes_ranges = ARRAY_SIZE(max2175_regmap_volatile_range), +}; + +static const struct reg_default max2175_reg_defaults[] = { + { 0x00, 0x07}, +}; + +static const struct regmap_config max2175_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xff, + .reg_defaults = max2175_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(max2175_reg_defaults), + .volatile_table = &max2175_volatile_regs, + .cache_type = REGCACHE_FLAT, +}; + +struct max2175 { + struct v4l2_subdev sd; /* Sub-device */ + struct i2c_client *client; /* I2C client */ + + /* Controls */ + struct v4l2_ctrl_handler ctrl_hdl; + struct v4l2_ctrl *lna_gain; /* LNA gain value */ + struct v4l2_ctrl *if_gain; /* I/F gain value */ + struct v4l2_ctrl *pll_lock; /* PLL lock */ + struct v4l2_ctrl *i2s_en; /* I2S output enable */ + struct v4l2_ctrl *hsls; /* High-side/Low-side polarity */ + struct v4l2_ctrl *rx_mode; /* Receive mode */ + + /* Regmap */ + struct regmap *regmap; + + /* Cached configuration */ + u32 freq; /* Tuned freq In Hz */ + const struct max2175_rxmode *rx_modes; /* EU or NA modes */ + const struct v4l2_frequency_band *bands_rf; /* EU or NA bands */ + + /* Device settings */ + unsigned long xtal_freq; /* Ref Oscillator freq in Hz */ + u32 decim_ratio; + bool master; /* Master/Slave */ + bool am_hiz; /* AM Hi-Z filter */ + + /* ROM values */ + u8 rom_bbf_bw_am; + u8 rom_bbf_bw_fm; + u8 rom_bbf_bw_dab; + + /* Driver private variables */ + bool mode_resolved; /* Flag to sanity check settings */ +}; + +static inline struct max2175 *max2175_from_sd(struct v4l2_subdev *sd) +{ + return container_of(sd, struct max2175, sd); +} + +static inline struct max2175 *max2175_from_ctrl_hdl(struct v4l2_ctrl_handler *h) +{ + return container_of(h, struct max2175, ctrl_hdl); +} + +/* Get bitval of a given val */ +static inline u8 max2175_get_bitval(u8 val, u8 msb, u8 lsb) +{ + return (val & GENMASK(msb, lsb)) >> lsb; +} + +/* Read/Write bit(s) on top of regmap */ +static int max2175_read(struct max2175 *ctx, u8 idx, u8 *val) +{ + u32 regval; + int ret; + + ret = regmap_read(ctx->regmap, idx, ®val); + if (ret) + mxm_err(ctx, "read ret(%d): idx 0x%02x\n", ret, idx); + else + *val = regval; + + return ret; +} + +static int max2175_write(struct max2175 *ctx, u8 idx, u8 val) +{ + int ret; + + ret = regmap_write(ctx->regmap, idx, val); + if (ret) + mxm_err(ctx, "write ret(%d): idx 0x%02x val 0x%02x\n", + ret, idx, val); + + return ret; +} + +static u8 max2175_read_bits(struct max2175 *ctx, u8 idx, u8 msb, u8 lsb) +{ + u8 val; + + if (max2175_read(ctx, idx, &val)) + return 0; + + return max2175_get_bitval(val, msb, lsb); +} + +static int max2175_write_bits(struct max2175 *ctx, u8 idx, + u8 msb, u8 lsb, u8 newval) +{ + int ret = regmap_update_bits(ctx->regmap, idx, GENMASK(msb, lsb), + newval << lsb); + + if (ret) + mxm_err(ctx, "wbits ret(%d): idx 0x%02x\n", ret, idx); + + return ret; +} + +static int max2175_write_bit(struct max2175 *ctx, u8 idx, u8 bit, u8 newval) +{ + return max2175_write_bits(ctx, idx, bit, bit, newval); +} + +/* Checks expected pattern every msec until timeout */ +static int max2175_poll_timeout(struct max2175 *ctx, u8 idx, u8 msb, u8 lsb, + u8 exp_bitval, u32 timeout_us) +{ + unsigned int val; + + return regmap_read_poll_timeout(ctx->regmap, idx, val, + (max2175_get_bitval(val, msb, lsb) == exp_bitval), + 1000, timeout_us); +} + +static int max2175_poll_csm_ready(struct max2175 *ctx) +{ + int ret; + + ret = max2175_poll_timeout(ctx, 69, 1, 1, 0, 50000); + if (ret) + mxm_err(ctx, "csm not ready\n"); + + return ret; +} + +#define MAX2175_IS_BAND_AM(ctx) \ + (max2175_read_bits(ctx, 5, 1, 0) == MAX2175_BAND_AM) + +#define MAX2175_IS_BAND_VHF(ctx) \ + (max2175_read_bits(ctx, 5, 1, 0) == MAX2175_BAND_VHF) + +#define MAX2175_IS_FM_MODE(ctx) \ + (max2175_read_bits(ctx, 12, 5, 4) == 0) + +#define MAX2175_IS_FMHD_MODE(ctx) \ + (max2175_read_bits(ctx, 12, 5, 4) == 1) + +#define MAX2175_IS_DAB_MODE(ctx) \ + (max2175_read_bits(ctx, 12, 5, 4) == 2) + +static int max2175_band_from_freq(u32 freq) +{ + if (freq >= 144000 && freq <= 26100000) + return MAX2175_BAND_AM; + else if (freq >= 65000000 && freq <= 108000000) + return MAX2175_BAND_FM; + + return MAX2175_BAND_VHF; +} + +static void max2175_i2s_enable(struct max2175 *ctx, bool enable) +{ + if (enable) + /* Stuff bits are zeroed */ + max2175_write_bits(ctx, 104, 3, 0, 2); + else + /* Keep SCK alive */ + max2175_write_bits(ctx, 104, 3, 0, 9); + mxm_dbg(ctx, "i2s %sabled\n", enable ? "en" : "dis"); +} + +static void max2175_set_filter_coeffs(struct max2175 *ctx, u8 m_sel, + u8 bank, const u16 *coeffs) +{ + unsigned int i; + u8 coeff_addr, upper_address = 24; + + mxm_dbg(ctx, "set_filter_coeffs: m_sel %d bank %d\n", m_sel, bank); + max2175_write_bits(ctx, 114, 5, 4, m_sel); + + if (m_sel == 2) + upper_address = 12; + + for (i = 0; i < upper_address; i++) { + coeff_addr = i + bank * 24; + max2175_write(ctx, 115, coeffs[i] >> 8); + max2175_write(ctx, 116, coeffs[i]); + max2175_write(ctx, 117, coeff_addr | 1 << 7); + } + max2175_write_bit(ctx, 117, 7, 0); +} + +static void max2175_load_fmeu_1p2(struct max2175 *ctx) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(fmeu1p2_map); i++) + max2175_write(ctx, fmeu1p2_map[i].idx, fmeu1p2_map[i].val); + + ctx->decim_ratio = 36; + + /* Load the Channel Filter Coefficients into channel filter bank #2 */ + max2175_set_filter_coeffs(ctx, MAX2175_CH_MSEL, 0, ch_coeff_fmeu); + max2175_set_filter_coeffs(ctx, MAX2175_EQ_MSEL, 0, + eq_coeff_fmeu1_ra02_m6db); +} + +static void max2175_load_dab_1p2(struct max2175 *ctx) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(dab12_map); i++) + max2175_write(ctx, dab12_map[i].idx, dab12_map[i].val); + + ctx->decim_ratio = 1; + + /* Load the Channel Filter Coefficients into channel filter bank #2 */ + max2175_set_filter_coeffs(ctx, MAX2175_CH_MSEL, 2, ch_coeff_dab1); +} + +static void max2175_load_fmna_1p0(struct max2175 *ctx) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(fmna1p0_map); i++) + max2175_write(ctx, fmna1p0_map[i].idx, fmna1p0_map[i].val); +} + +static void max2175_load_fmna_2p0(struct max2175 *ctx) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(fmna2p0_map); i++) + max2175_write(ctx, fmna2p0_map[i].idx, fmna2p0_map[i].val); +} + +static void max2175_set_bbfilter(struct max2175 *ctx) +{ + if (MAX2175_IS_BAND_AM(ctx)) { + max2175_write_bits(ctx, 12, 3, 0, ctx->rom_bbf_bw_am); + mxm_dbg(ctx, "set_bbfilter AM: rom %d\n", ctx->rom_bbf_bw_am); + } else if (MAX2175_IS_DAB_MODE(ctx)) { + max2175_write_bits(ctx, 12, 3, 0, ctx->rom_bbf_bw_dab); + mxm_dbg(ctx, "set_bbfilter DAB: rom %d\n", ctx->rom_bbf_bw_dab); + } else { + max2175_write_bits(ctx, 12, 3, 0, ctx->rom_bbf_bw_fm); + mxm_dbg(ctx, "set_bbfilter FM: rom %d\n", ctx->rom_bbf_bw_fm); + } +} + +static bool max2175_set_csm_mode(struct max2175 *ctx, + enum max2175_csm_mode new_mode) +{ + int ret = max2175_poll_csm_ready(ctx); + + if (ret) + return ret; + + max2175_write_bits(ctx, 0, 2, 0, new_mode); + mxm_dbg(ctx, "set csm new mode %d\n", new_mode); + + /* Wait for a fixed settle down time depending on new mode */ + switch (new_mode) { + case MAX2175_PRESET_TUNE: + usleep_range(51100, 51500); /* 51.1ms */ + break; + /* + * Other mode switches need different sleep values depending on band & + * mode + */ + default: + break; + } + + return max2175_poll_csm_ready(ctx); +} + +static int max2175_csm_action(struct max2175 *ctx, + enum max2175_csm_mode action) +{ + int ret; + + mxm_dbg(ctx, "csm_action: %d\n", action); + + /* Other actions can be added in future when needed */ + ret = max2175_set_csm_mode(ctx, MAX2175_LOAD_TO_BUFFER); + if (ret) + return ret; + + return max2175_set_csm_mode(ctx, MAX2175_PRESET_TUNE); +} + +static int max2175_set_lo_freq(struct max2175 *ctx, u32 lo_freq) +{ + u8 lo_mult, loband_bits = 0, vcodiv_bits = 0; + u32 int_desired, frac_desired; + enum max2175_band band; + int ret; + + band = max2175_read_bits(ctx, 5, 1, 0); + switch (band) { + case MAX2175_BAND_AM: + lo_mult = 16; + break; + case MAX2175_BAND_FM: + if (lo_freq <= 74700000) { + lo_mult = 16; + } else if (lo_freq > 74700000 && lo_freq <= 110000000) { + loband_bits = 1; + lo_mult = 8; + } else { + loband_bits = 1; + vcodiv_bits = 3; + lo_mult = 8; + } + break; + case MAX2175_BAND_VHF: + if (lo_freq <= 210000000) + vcodiv_bits = 2; + else + vcodiv_bits = 1; + + loband_bits = 2; + lo_mult = 4; + break; + default: + loband_bits = 3; + vcodiv_bits = 2; + lo_mult = 2; + break; + } + + if (band == MAX2175_BAND_L) + lo_freq /= lo_mult; + else + lo_freq *= lo_mult; + + int_desired = lo_freq / ctx->xtal_freq; + frac_desired = div_u64((u64)(lo_freq % ctx->xtal_freq) << 20, + ctx->xtal_freq); + + /* Check CSM is not busy */ + ret = max2175_poll_csm_ready(ctx); + if (ret) + return ret; + + mxm_dbg(ctx, "lo_mult %u int %u frac %u\n", + lo_mult, int_desired, frac_desired); + + /* Write the calculated values to the appropriate registers */ + max2175_write(ctx, 1, int_desired); + max2175_write_bits(ctx, 2, 3, 0, (frac_desired >> 16) & 0xf); + max2175_write(ctx, 3, frac_desired >> 8); + max2175_write(ctx, 4, frac_desired); + max2175_write_bits(ctx, 5, 3, 2, loband_bits); + max2175_write_bits(ctx, 6, 7, 6, vcodiv_bits); + + return ret; +} + +/* + * Helper similar to DIV_ROUND_CLOSEST but an inline function that accepts s64 + * dividend and s32 divisor + */ +static inline s64 max2175_round_closest(s64 dividend, s32 divisor) +{ + if ((dividend > 0 && divisor > 0) || (dividend < 0 && divisor < 0)) + return div_s64(dividend + divisor / 2, divisor); + + return div_s64(dividend - divisor / 2, divisor); +} + +static int max2175_set_nco_freq(struct max2175 *ctx, s32 nco_freq) +{ + s32 clock_rate = ctx->xtal_freq / ctx->decim_ratio; + u32 nco_reg, abs_nco_freq = abs(nco_freq); + s64 nco_val_desired; + int ret; + + if (abs_nco_freq < clock_rate / 2) { + nco_val_desired = 2 * nco_freq; + } else { + nco_val_desired = 2 * (clock_rate - abs_nco_freq); + if (nco_freq < 0) + nco_val_desired = -nco_val_desired; + } + + nco_reg = max2175_round_closest(nco_val_desired << 20, clock_rate); + + if (nco_freq < 0) + nco_reg += 0x200000; + + /* Check CSM is not busy */ + ret = max2175_poll_csm_ready(ctx); + if (ret) + return ret; + + mxm_dbg(ctx, "freq %d desired %lld reg %u\n", + nco_freq, nco_val_desired, nco_reg); + + /* Write the calculated values to the appropriate registers */ + max2175_write_bits(ctx, 7, 4, 0, (nco_reg >> 16) & 0x1f); + max2175_write(ctx, 8, nco_reg >> 8); + max2175_write(ctx, 9, nco_reg); + + return ret; +} + +static int max2175_set_rf_freq_non_am_bands(struct max2175 *ctx, u64 freq, + u32 lo_pos) +{ + s64 adj_freq, low_if_freq; + int ret; + + mxm_dbg(ctx, "rf_freq: non AM bands\n"); + + if (MAX2175_IS_FM_MODE(ctx)) + low_if_freq = 128000; + else if (MAX2175_IS_FMHD_MODE(ctx)) + low_if_freq = 228000; + else + return max2175_set_lo_freq(ctx, freq); + + if (MAX2175_IS_BAND_VHF(ctx) == (lo_pos == MAX2175_LO_ABOVE_DESIRED)) + adj_freq = freq + low_if_freq; + else + adj_freq = freq - low_if_freq; + + ret = max2175_set_lo_freq(ctx, adj_freq); + if (ret) + return ret; + + return max2175_set_nco_freq(ctx, -low_if_freq); +} + +static int max2175_set_rf_freq(struct max2175 *ctx, u64 freq, u32 lo_pos) +{ + int ret; + + if (MAX2175_IS_BAND_AM(ctx)) + ret = max2175_set_nco_freq(ctx, freq); + else + ret = max2175_set_rf_freq_non_am_bands(ctx, freq, lo_pos); + + mxm_dbg(ctx, "set_rf_freq: ret %d freq %llu\n", ret, freq); + + return ret; +} + +static int max2175_tune_rf_freq(struct max2175 *ctx, u64 freq, u32 hsls) +{ + int ret; + + ret = max2175_set_rf_freq(ctx, freq, hsls); + if (ret) + return ret; + + ret = max2175_csm_action(ctx, MAX2175_BUFFER_PLUS_PRESET_TUNE); + if (ret) + return ret; + + mxm_dbg(ctx, "tune_rf_freq: old %u new %llu\n", ctx->freq, freq); + ctx->freq = freq; + + return ret; +} + +static void max2175_set_hsls(struct max2175 *ctx, u32 lo_pos) +{ + mxm_dbg(ctx, "set_hsls: lo_pos %u\n", lo_pos); + + if ((lo_pos == MAX2175_LO_BELOW_DESIRED) == MAX2175_IS_BAND_VHF(ctx)) + max2175_write_bit(ctx, 5, 4, 1); + else + max2175_write_bit(ctx, 5, 4, 0); +} + +static void max2175_set_eu_rx_mode(struct max2175 *ctx, u32 rx_mode) +{ + switch (rx_mode) { + case MAX2175_EU_FM_1_2: + max2175_load_fmeu_1p2(ctx); + break; + + case MAX2175_DAB_1_2: + max2175_load_dab_1p2(ctx); + break; + } + /* Master is the default setting */ + if (!ctx->master) + max2175_write_bit(ctx, 30, 7, 1); +} + +static void max2175_set_na_rx_mode(struct max2175 *ctx, u32 rx_mode) +{ + switch (rx_mode) { + case MAX2175_NA_FM_1_0: + max2175_load_fmna_1p0(ctx); + break; + case MAX2175_NA_FM_2_0: + max2175_load_fmna_2p0(ctx); + break; + } + /* Master is the default setting */ + if (!ctx->master) + max2175_write_bit(ctx, 30, 7, 1); + + ctx->decim_ratio = 27; + + /* Load the Channel Filter Coefficients into channel filter bank #2 */ + max2175_set_filter_coeffs(ctx, MAX2175_CH_MSEL, 0, ch_coeff_fmna); + max2175_set_filter_coeffs(ctx, MAX2175_EQ_MSEL, 0, + eq_coeff_fmna1_ra02_m6db); +} + +static int max2175_set_rx_mode(struct max2175 *ctx, u32 rx_mode) +{ + mxm_dbg(ctx, "set_rx_mode: %u am_hiz %u\n", rx_mode, ctx->am_hiz); + if (ctx->xtal_freq == MAX2175_EU_XTAL_FREQ) + max2175_set_eu_rx_mode(ctx, rx_mode); + else + max2175_set_na_rx_mode(ctx, rx_mode); + + if (ctx->am_hiz) { + mxm_dbg(ctx, "setting AM HiZ related config\n"); + max2175_write_bit(ctx, 50, 5, 1); + max2175_write_bit(ctx, 90, 7, 1); + max2175_write_bits(ctx, 73, 1, 0, 2); + max2175_write_bits(ctx, 80, 5, 0, 33); + } + + /* Load BB filter trim values saved in ROM */ + max2175_set_bbfilter(ctx); + + /* Set HSLS */ + max2175_set_hsls(ctx, ctx->hsls->cur.val); + + /* Use i2s enable settings */ + max2175_i2s_enable(ctx, ctx->i2s_en->cur.val); + + ctx->mode_resolved = true; + + return 0; +} + +static int max2175_rx_mode_from_freq(struct max2175 *ctx, u32 freq, u32 *mode) +{ + unsigned int i; + int band = max2175_band_from_freq(freq); + + /* Pick the first match always */ + for (i = 0; i <= ctx->rx_mode->maximum; i++) { + if (ctx->rx_modes[i].band == band) { + *mode = i; + mxm_dbg(ctx, "rx_mode_from_freq: freq %u mode %d\n", + freq, *mode); + return 0; + } + } + + return -EINVAL; +} + +static bool max2175_freq_rx_mode_valid(struct max2175 *ctx, + u32 mode, u32 freq) +{ + int band = max2175_band_from_freq(freq); + + return (ctx->rx_modes[mode].band == band); +} + +static void max2175_load_adc_presets(struct max2175 *ctx) +{ + unsigned int i, j; + + for (i = 0; i < ARRAY_SIZE(adc_presets); i++) + for (j = 0; j < ARRAY_SIZE(adc_presets[0]); j++) + max2175_write(ctx, 146 + j + i * 55, adc_presets[i][j]); +} + +static int max2175_init_power_manager(struct max2175 *ctx) +{ + int ret; + + /* Execute on-chip power-up/calibration */ + max2175_write_bit(ctx, 99, 2, 0); + usleep_range(1000, 1500); + max2175_write_bit(ctx, 99, 2, 1); + + /* Wait for the power manager to finish. */ + ret = max2175_poll_timeout(ctx, 69, 7, 7, 1, 50000); + if (ret) + mxm_err(ctx, "init pm failed\n"); + + return ret; +} + +static int max2175_recalibrate_adc(struct max2175 *ctx) +{ + int ret; + + /* ADC Re-calibration */ + max2175_write(ctx, 150, 0xff); + max2175_write(ctx, 205, 0xff); + max2175_write(ctx, 147, 0x20); + max2175_write(ctx, 147, 0x00); + max2175_write(ctx, 202, 0x20); + max2175_write(ctx, 202, 0x00); + + ret = max2175_poll_timeout(ctx, 69, 4, 3, 3, 50000); + if (ret) + mxm_err(ctx, "adc recalibration failed\n"); + + return ret; +} + +static u8 max2175_read_rom(struct max2175 *ctx, u8 row) +{ + u8 data = 0; + + max2175_write_bit(ctx, 56, 4, 0); + max2175_write_bits(ctx, 56, 3, 0, row); + + usleep_range(2000, 2500); + max2175_read(ctx, 58, &data); + + max2175_write_bits(ctx, 56, 3, 0, 0); + + mxm_dbg(ctx, "read_rom: row %d data 0x%02x\n", row, data); + + return data; +} + +static void max2175_load_from_rom(struct max2175 *ctx) +{ + u8 data = 0; + + data = max2175_read_rom(ctx, 0); + ctx->rom_bbf_bw_am = data & 0x0f; + max2175_write_bits(ctx, 81, 3, 0, data >> 4); + + data = max2175_read_rom(ctx, 1); + ctx->rom_bbf_bw_fm = data & 0x0f; + ctx->rom_bbf_bw_dab = data >> 4; + + data = max2175_read_rom(ctx, 2); + max2175_write_bits(ctx, 82, 4, 0, data & 0x1f); + max2175_write_bits(ctx, 82, 7, 5, data >> 5); + + data = max2175_read_rom(ctx, 3); + if (ctx->am_hiz) { + data &= 0x0f; + data |= (max2175_read_rom(ctx, 7) & 0x40) >> 2; + if (!data) + data |= 2; + } else { + data = (data & 0xf0) >> 4; + data |= (max2175_read_rom(ctx, 7) & 0x80) >> 3; + if (!data) + data |= 30; + } + max2175_write_bits(ctx, 80, 5, 0, data + 31); + + data = max2175_read_rom(ctx, 6); + max2175_write_bits(ctx, 81, 7, 6, data >> 6); +} + +static void max2175_load_full_fm_eu_1p0(struct max2175 *ctx) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(full_fm_eu_1p0); i++) + max2175_write(ctx, i + 1, full_fm_eu_1p0[i]); + + usleep_range(5000, 5500); + ctx->decim_ratio = 36; +} + +static void max2175_load_full_fm_na_1p0(struct max2175 *ctx) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(full_fm_na_1p0); i++) + max2175_write(ctx, i + 1, full_fm_na_1p0[i]); + + usleep_range(5000, 5500); + ctx->decim_ratio = 27; +} + +static int max2175_core_init(struct max2175 *ctx, u32 refout_bits) +{ + int ret; + + /* MAX2175 uses 36.864MHz clock for EU & 40.154MHz for NA region */ + if (ctx->xtal_freq == MAX2175_EU_XTAL_FREQ) + max2175_load_full_fm_eu_1p0(ctx); + else + max2175_load_full_fm_na_1p0(ctx); + + /* The default settings assume master */ + if (!ctx->master) + max2175_write_bit(ctx, 30, 7, 1); + + mxm_dbg(ctx, "refout_bits %u\n", refout_bits); + + /* Set REFOUT */ + max2175_write_bits(ctx, 56, 7, 5, refout_bits); + + /* ADC Reset */ + max2175_write_bit(ctx, 99, 1, 0); + usleep_range(1000, 1500); + max2175_write_bit(ctx, 99, 1, 1); + + /* Load ADC preset values */ + max2175_load_adc_presets(ctx); + + /* Initialize the power management state machine */ + ret = max2175_init_power_manager(ctx); + if (ret) + return ret; + + /* Recalibrate ADC */ + ret = max2175_recalibrate_adc(ctx); + if (ret) + return ret; + + /* Load ROM values to appropriate registers */ + max2175_load_from_rom(ctx); + + if (ctx->xtal_freq == MAX2175_EU_XTAL_FREQ) { + /* Load FIR coefficients into bank 0 */ + max2175_set_filter_coeffs(ctx, MAX2175_CH_MSEL, 0, + ch_coeff_fmeu); + max2175_set_filter_coeffs(ctx, MAX2175_EQ_MSEL, 0, + eq_coeff_fmeu1_ra02_m6db); + } else { + /* Load FIR coefficients into bank 0 */ + max2175_set_filter_coeffs(ctx, MAX2175_CH_MSEL, 0, + ch_coeff_fmna); + max2175_set_filter_coeffs(ctx, MAX2175_EQ_MSEL, 0, + eq_coeff_fmna1_ra02_m6db); + } + mxm_dbg(ctx, "core initialized\n"); + + return 0; +} + +static void max2175_s_ctrl_rx_mode(struct max2175 *ctx, u32 rx_mode) +{ + /* Load mode. Range check already done */ + max2175_set_rx_mode(ctx, rx_mode); + + mxm_dbg(ctx, "s_ctrl_rx_mode: %u curr freq %u\n", rx_mode, ctx->freq); + + /* Check if current freq valid for mode & update */ + if (max2175_freq_rx_mode_valid(ctx, rx_mode, ctx->freq)) + max2175_tune_rf_freq(ctx, ctx->freq, ctx->hsls->cur.val); + else + /* Use default freq of mode if current freq is not valid */ + max2175_tune_rf_freq(ctx, ctx->rx_modes[rx_mode].freq, + ctx->hsls->cur.val); +} + +static int max2175_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct max2175 *ctx = max2175_from_ctrl_hdl(ctrl->handler); + + mxm_dbg(ctx, "s_ctrl: id 0x%x, val %u\n", ctrl->id, ctrl->val); + switch (ctrl->id) { + case V4L2_CID_MAX2175_I2S_ENABLE: + max2175_i2s_enable(ctx, ctrl->val); + break; + case V4L2_CID_MAX2175_HSLS: + max2175_set_hsls(ctx, ctrl->val); + break; + case V4L2_CID_MAX2175_RX_MODE: + max2175_s_ctrl_rx_mode(ctx, ctrl->val); + break; + } + + return 0; +} + +static u32 max2175_get_lna_gain(struct max2175 *ctx) +{ + enum max2175_band band = max2175_read_bits(ctx, 5, 1, 0); + + switch (band) { + case MAX2175_BAND_AM: + return max2175_read_bits(ctx, 51, 3, 0); + case MAX2175_BAND_FM: + return max2175_read_bits(ctx, 50, 3, 0); + case MAX2175_BAND_VHF: + return max2175_read_bits(ctx, 52, 5, 0); + default: + return 0; + } +} + +static int max2175_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct max2175 *ctx = max2175_from_ctrl_hdl(ctrl->handler); + + switch (ctrl->id) { + case V4L2_CID_RF_TUNER_LNA_GAIN: + ctrl->val = max2175_get_lna_gain(ctx); + break; + case V4L2_CID_RF_TUNER_IF_GAIN: + ctrl->val = max2175_read_bits(ctx, 49, 4, 0); + break; + case V4L2_CID_RF_TUNER_PLL_LOCK: + ctrl->val = (max2175_read_bits(ctx, 60, 7, 6) == 3); + break; + } + + return 0; +}; + +static int max2175_set_freq_and_mode(struct max2175 *ctx, u32 freq) +{ + u32 rx_mode; + int ret; + + /* Get band from frequency */ + ret = max2175_rx_mode_from_freq(ctx, freq, &rx_mode); + if (ret) + return ret; + + mxm_dbg(ctx, "set_freq_and_mode: freq %u rx_mode %d\n", freq, rx_mode); + + /* Load mode */ + max2175_set_rx_mode(ctx, rx_mode); + ctx->rx_mode->cur.val = rx_mode; + + /* Tune to the new freq given */ + return max2175_tune_rf_freq(ctx, freq, ctx->hsls->cur.val); +} + +static int max2175_s_frequency(struct v4l2_subdev *sd, + const struct v4l2_frequency *vf) +{ + struct max2175 *ctx = max2175_from_sd(sd); + u32 freq; + int ret = 0; + + mxm_dbg(ctx, "s_freq: new %u curr %u, mode_resolved %d\n", + vf->frequency, ctx->freq, ctx->mode_resolved); + + if (vf->tuner != 0) + return -EINVAL; + + freq = clamp(vf->frequency, ctx->bands_rf->rangelow, + ctx->bands_rf->rangehigh); + + /* Check new freq valid for rx_mode if already resolved */ + if (ctx->mode_resolved && + max2175_freq_rx_mode_valid(ctx, ctx->rx_mode->cur.val, freq)) + ret = max2175_tune_rf_freq(ctx, freq, ctx->hsls->cur.val); + else + /* Find default rx_mode for freq and tune to it */ + ret = max2175_set_freq_and_mode(ctx, freq); + + mxm_dbg(ctx, "s_freq: ret %d curr %u mode_resolved %d mode %u\n", + ret, ctx->freq, ctx->mode_resolved, ctx->rx_mode->cur.val); + + return ret; +} + +static int max2175_g_frequency(struct v4l2_subdev *sd, + struct v4l2_frequency *vf) +{ + struct max2175 *ctx = max2175_from_sd(sd); + int ret = 0; + + if (vf->tuner != 0) + return -EINVAL; + + /* RF freq */ + vf->type = V4L2_TUNER_RF; + vf->frequency = ctx->freq; + + return ret; +} + +static int max2175_enum_freq_bands(struct v4l2_subdev *sd, + struct v4l2_frequency_band *band) +{ + struct max2175 *ctx = max2175_from_sd(sd); + + if (band->tuner != 0 || band->index != 0) + return -EINVAL; + + *band = *ctx->bands_rf; + + return 0; +} + +static int max2175_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt) +{ + struct max2175 *ctx = max2175_from_sd(sd); + + if (vt->index > 0) + return -EINVAL; + + strlcpy(vt->name, "RF", sizeof(vt->name)); + vt->type = V4L2_TUNER_RF; + vt->capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS; + vt->rangelow = ctx->bands_rf->rangelow; + vt->rangehigh = ctx->bands_rf->rangehigh; + + return 0; +} + +static int max2175_s_tuner(struct v4l2_subdev *sd, const struct v4l2_tuner *vt) +{ + /* Check tuner index is valid */ + if (vt->index > 0) + return -EINVAL; + + return 0; +} + +static const struct v4l2_subdev_tuner_ops max2175_tuner_ops = { + .s_frequency = max2175_s_frequency, + .g_frequency = max2175_g_frequency, + .enum_freq_bands = max2175_enum_freq_bands, + .g_tuner = max2175_g_tuner, + .s_tuner = max2175_s_tuner, +}; + +static const struct v4l2_subdev_ops max2175_ops = { + .tuner = &max2175_tuner_ops, +}; + +static const struct v4l2_ctrl_ops max2175_ctrl_ops = { + .s_ctrl = max2175_s_ctrl, + .g_volatile_ctrl = max2175_g_volatile_ctrl, +}; + +/* + * I2S output enable/disable configuration. This is a private control. + * Refer to Documentation/media/v4l-drivers/max2175 for more details. + */ +static const struct v4l2_ctrl_config max2175_i2s_en = { + .ops = &max2175_ctrl_ops, + .id = V4L2_CID_MAX2175_I2S_ENABLE, + .name = "I2S Enable", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = 0, + .max = 1, + .step = 1, + .def = 1, + .is_private = 1, +}; + +/* + * HSLS value control LO freq adjacent location configuration. + * Refer to Documentation/media/v4l-drivers/max2175 for more details. + */ +static const struct v4l2_ctrl_config max2175_hsls = { + .ops = &max2175_ctrl_ops, + .id = V4L2_CID_MAX2175_HSLS, + .name = "HSLS Above/Below Desired", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = 0, + .max = 1, + .step = 1, + .def = 1, +}; + +/* + * Rx modes below are a set of preset configurations that decides the tuner's + * sck and sample rate of transmission. They are separate for EU & NA regions. + * Refer to Documentation/media/v4l-drivers/max2175 for more details. + */ +static const char * const max2175_ctrl_eu_rx_modes[] = { + [MAX2175_EU_FM_1_2] = "EU FM 1.2", + [MAX2175_DAB_1_2] = "DAB 1.2", +}; + +static const char * const max2175_ctrl_na_rx_modes[] = { + [MAX2175_NA_FM_1_0] = "NA FM 1.0", + [MAX2175_NA_FM_2_0] = "NA FM 2.0", +}; + +static const struct v4l2_ctrl_config max2175_eu_rx_mode = { + .ops = &max2175_ctrl_ops, + .id = V4L2_CID_MAX2175_RX_MODE, + .name = "RX Mode", + .type = V4L2_CTRL_TYPE_MENU, + .max = ARRAY_SIZE(max2175_ctrl_eu_rx_modes) - 1, + .def = 0, + .qmenu = max2175_ctrl_eu_rx_modes, +}; + +static const struct v4l2_ctrl_config max2175_na_rx_mode = { + .ops = &max2175_ctrl_ops, + .id = V4L2_CID_MAX2175_RX_MODE, + .name = "RX Mode", + .type = V4L2_CTRL_TYPE_MENU, + .max = ARRAY_SIZE(max2175_ctrl_na_rx_modes) - 1, + .def = 0, + .qmenu = max2175_ctrl_na_rx_modes, +}; + +static int max2175_refout_load_to_bits(struct i2c_client *client, u32 load, + u32 *bits) +{ + if (load <= 40) + *bits = load / 10; + else if (load >= 60 && load <= 70) + *bits = load / 10 - 1; + else + return -EINVAL; + + return 0; +} + +static int max2175_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + bool master = true, am_hiz = false; + u32 refout_load, refout_bits = 0; /* REFOUT disabled */ + struct v4l2_ctrl_handler *hdl; + struct fwnode_handle *fwnode; + struct device_node *np; + struct v4l2_subdev *sd; + struct regmap *regmap; + struct max2175 *ctx; + struct clk *clk; + int ret; + + /* Parse DT properties */ + np = of_parse_phandle(client->dev.of_node, "maxim,master", 0); + if (np) { + master = false; /* Slave tuner */ + of_node_put(np); + } + + fwnode = of_fwnode_handle(client->dev.of_node); + if (fwnode_property_present(fwnode, "maxim,am-hiz-filter")) + am_hiz = true; + + if (!fwnode_property_read_u32(fwnode, "maxim,refout-load", + &refout_load)) { + ret = max2175_refout_load_to_bits(client, refout_load, + &refout_bits); + if (ret) { + dev_err(&client->dev, "invalid refout_load %u\n", + refout_load); + return -EINVAL; + } + } + + clk = devm_clk_get(&client->dev, NULL); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + dev_err(&client->dev, "cannot get clock %d\n", ret); + return -ENODEV; + } + + regmap = devm_regmap_init_i2c(client, &max2175_regmap_config); + if (IS_ERR(regmap)) { + ret = PTR_ERR(regmap); + dev_err(&client->dev, "regmap init failed %d\n", ret); + return -ENODEV; + } + + /* Alloc tuner context */ + ctx = devm_kzalloc(&client->dev, sizeof(*ctx), GFP_KERNEL); + if (ctx == NULL) + return -ENOMEM; + + sd = &ctx->sd; + ctx->master = master; + ctx->am_hiz = am_hiz; + ctx->mode_resolved = false; + ctx->regmap = regmap; + ctx->xtal_freq = clk_get_rate(clk); + dev_info(&client->dev, "xtal freq %luHz\n", ctx->xtal_freq); + + v4l2_i2c_subdev_init(sd, client, &max2175_ops); + ctx->client = client; + + sd->flags = V4L2_SUBDEV_FL_HAS_DEVNODE; + + /* Controls */ + hdl = &ctx->ctrl_hdl; + ret = v4l2_ctrl_handler_init(hdl, 7); + if (ret) + return ret; + + ctx->lna_gain = v4l2_ctrl_new_std(hdl, &max2175_ctrl_ops, + V4L2_CID_RF_TUNER_LNA_GAIN, + 0, 63, 1, 0); + ctx->lna_gain->flags |= (V4L2_CTRL_FLAG_VOLATILE | + V4L2_CTRL_FLAG_READ_ONLY); + ctx->if_gain = v4l2_ctrl_new_std(hdl, &max2175_ctrl_ops, + V4L2_CID_RF_TUNER_IF_GAIN, + 0, 31, 1, 0); + ctx->if_gain->flags |= (V4L2_CTRL_FLAG_VOLATILE | + V4L2_CTRL_FLAG_READ_ONLY); + ctx->pll_lock = v4l2_ctrl_new_std(hdl, &max2175_ctrl_ops, + V4L2_CID_RF_TUNER_PLL_LOCK, + 0, 1, 1, 0); + ctx->pll_lock->flags |= (V4L2_CTRL_FLAG_VOLATILE | + V4L2_CTRL_FLAG_READ_ONLY); + ctx->i2s_en = v4l2_ctrl_new_custom(hdl, &max2175_i2s_en, NULL); + ctx->hsls = v4l2_ctrl_new_custom(hdl, &max2175_hsls, NULL); + + if (ctx->xtal_freq == MAX2175_EU_XTAL_FREQ) { + ctx->rx_mode = v4l2_ctrl_new_custom(hdl, + &max2175_eu_rx_mode, NULL); + ctx->rx_modes = eu_rx_modes; + ctx->bands_rf = &eu_bands_rf; + } else { + ctx->rx_mode = v4l2_ctrl_new_custom(hdl, + &max2175_na_rx_mode, NULL); + ctx->rx_modes = na_rx_modes; + ctx->bands_rf = &na_bands_rf; + } + ctx->sd.ctrl_handler = &ctx->ctrl_hdl; + + /* Set the defaults */ + ctx->freq = ctx->bands_rf->rangelow; + + /* Register subdev */ + ret = v4l2_async_register_subdev(sd); + if (ret) { + dev_err(&client->dev, "register subdev failed\n"); + goto err_reg; + } + + /* Initialize device */ + ret = max2175_core_init(ctx, refout_bits); + if (ret) + goto err_init; + + ret = v4l2_ctrl_handler_setup(hdl); + if (ret) + goto err_init; + + return 0; + +err_init: + v4l2_async_unregister_subdev(sd); +err_reg: + v4l2_ctrl_handler_free(&ctx->ctrl_hdl); + + return ret; +} + +static int max2175_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct max2175 *ctx = max2175_from_sd(sd); + + v4l2_ctrl_handler_free(&ctx->ctrl_hdl); + v4l2_async_unregister_subdev(sd); + + return 0; +} + +static const struct i2c_device_id max2175_id[] = { + { DRIVER_NAME, 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, max2175_id); + +static const struct of_device_id max2175_of_ids[] = { + { .compatible = "maxim,max2175", }, + { } +}; +MODULE_DEVICE_TABLE(of, max2175_of_ids); + +static struct i2c_driver max2175_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = max2175_of_ids, + }, + .probe = max2175_probe, + .remove = max2175_remove, + .id_table = max2175_id, +}; + +module_i2c_driver(max2175_driver); + +MODULE_DESCRIPTION("Maxim MAX2175 RF to Bits tuner driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Ramesh Shanmugasundaram <ramesh.shanmugasundaram@bp.renesas.com>"); diff --git a/drivers/media/i2c/max2175.h b/drivers/media/i2c/max2175.h new file mode 100644 index 000000000000..eb43373ce7e2 --- /dev/null +++ b/drivers/media/i2c/max2175.h @@ -0,0 +1,109 @@ +/* + * Maxim Integrated MAX2175 RF to Bits tuner driver + * + * This driver & most of the hard coded values are based on the reference + * application delivered by Maxim for this device. + * + * Copyright (C) 2016 Maxim Integrated Products + * Copyright (C) 2017 Renesas Electronics Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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. + */ + +#ifndef __MAX2175_H__ +#define __MAX2175_H__ + +#define MAX2175_EU_XTAL_FREQ 36864000 /* In Hz */ +#define MAX2175_NA_XTAL_FREQ 40186125 /* In Hz */ + +enum max2175_region { + MAX2175_REGION_EU = 0, /* Europe */ + MAX2175_REGION_NA, /* North America */ +}; + +enum max2175_band { + MAX2175_BAND_AM = 0, + MAX2175_BAND_FM, + MAX2175_BAND_VHF, + MAX2175_BAND_L, +}; + +enum max2175_eu_mode { + /* EU modes */ + MAX2175_EU_FM_1_2 = 0, + MAX2175_DAB_1_2, + + /* + * Other possible modes to add in future + * MAX2175_DAB_1_0, + * MAX2175_DAB_1_3, + * MAX2175_EU_FM_2_2, + * MAX2175_EU_FMHD_4_0, + * MAX2175_EU_AM_1_0, + * MAX2175_EU_AM_2_2, + */ +}; + +enum max2175_na_mode { + /* NA modes */ + MAX2175_NA_FM_1_0 = 0, + MAX2175_NA_FM_2_0, + + /* + * Other possible modes to add in future + * MAX2175_NA_FMHD_1_0, + * MAX2175_NA_FMHD_1_2, + * MAX2175_NA_AM_1_0, + * MAX2175_NA_AM_1_2, + */ +}; + +/* Supported I2S modes */ +enum { + MAX2175_I2S_MODE0 = 0, + MAX2175_I2S_MODE1, + MAX2175_I2S_MODE2, + MAX2175_I2S_MODE3, + MAX2175_I2S_MODE4, +}; + +/* Coefficient table groups */ +enum { + MAX2175_CH_MSEL = 0, + MAX2175_EQ_MSEL, + MAX2175_AA_MSEL, +}; + +/* HSLS LO injection polarity */ +enum { + MAX2175_LO_BELOW_DESIRED = 0, + MAX2175_LO_ABOVE_DESIRED, +}; + +/* Channel FSM modes */ +enum max2175_csm_mode { + MAX2175_LOAD_TO_BUFFER = 0, + MAX2175_PRESET_TUNE, + MAX2175_SEARCH, + MAX2175_AF_UPDATE, + MAX2175_JUMP_FAST_TUNE, + MAX2175_CHECK, + MAX2175_LOAD_AND_SWAP, + MAX2175_END, + MAX2175_BUFFER_PLUS_PRESET_TUNE, + MAX2175_BUFFER_PLUS_SEARCH, + MAX2175_BUFFER_PLUS_AF_UPDATE, + MAX2175_BUFFER_PLUS_JUMP_FAST_TUNE, + MAX2175_BUFFER_PLUS_CHECK, + MAX2175_BUFFER_PLUS_LOAD_AND_SWAP, + MAX2175_NO_ACTION +}; + +#endif /* __MAX2175_H__ */ diff --git a/drivers/media/i2c/msp3400-kthreads.c b/drivers/media/i2c/msp3400-kthreads.c index 11fc593ed908..4dd01e9f553b 100644 --- a/drivers/media/i2c/msp3400-kthreads.c +++ b/drivers/media/i2c/msp3400-kthreads.c @@ -655,6 +655,7 @@ restart: break; case 0: /* 4.5 */ state->detected_std = V4L2_STD_MN; + /* fall-through */ default: no_second: state->second = msp3400c_carrier_detect_main[max1].cdo; diff --git a/drivers/media/i2c/mt9v032.c b/drivers/media/i2c/mt9v032.c index 2e7a6e62a358..8a430640c85d 100644 --- a/drivers/media/i2c/mt9v032.c +++ b/drivers/media/i2c/mt9v032.c @@ -19,6 +19,7 @@ #include <linux/log2.h> #include <linux/mutex.h> #include <linux/of.h> +#include <linux/of_graph.h> #include <linux/regmap.h> #include <linux/slab.h> #include <linux/videodev2.h> @@ -28,7 +29,7 @@ #include <media/i2c/mt9v032.h> #include <media/v4l2-ctrls.h> #include <media/v4l2-device.h> -#include <media/v4l2-of.h> +#include <media/v4l2-fwnode.h> #include <media/v4l2-subdev.h> /* The first four rows are black rows. The active area spans 753x481 pixels. */ @@ -979,7 +980,7 @@ static struct mt9v032_platform_data * mt9v032_get_pdata(struct i2c_client *client) { struct mt9v032_platform_data *pdata = NULL; - struct v4l2_of_endpoint endpoint; + struct v4l2_fwnode_endpoint endpoint; struct device_node *np; struct property *prop; @@ -990,7 +991,7 @@ mt9v032_get_pdata(struct i2c_client *client) if (!np) return NULL; - if (v4l2_of_parse_endpoint(np, &endpoint) < 0) + if (v4l2_fwnode_endpoint_parse(of_fwnode_handle(np), &endpoint) < 0) goto done; pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL); diff --git a/drivers/media/i2c/ov13858.c b/drivers/media/i2c/ov13858.c new file mode 100644 index 000000000000..86550d8ddfee --- /dev/null +++ b/drivers/media/i2c/ov13858.c @@ -0,0 +1,1816 @@ +/* + * Copyright (c) 2017 Intel Corporation. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * 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. + * + */ + +#include <linux/acpi.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> + +#define OV13858_REG_VALUE_08BIT 1 +#define OV13858_REG_VALUE_16BIT 2 +#define OV13858_REG_VALUE_24BIT 3 + +#define OV13858_REG_MODE_SELECT 0x0100 +#define OV13858_MODE_STANDBY 0x00 +#define OV13858_MODE_STREAMING 0x01 + +#define OV13858_REG_SOFTWARE_RST 0x0103 +#define OV13858_SOFTWARE_RST 0x01 + +/* PLL1 generates PCLK and MIPI_PHY_CLK */ +#define OV13858_REG_PLL1_CTRL_0 0x0300 +#define OV13858_REG_PLL1_CTRL_1 0x0301 +#define OV13858_REG_PLL1_CTRL_2 0x0302 +#define OV13858_REG_PLL1_CTRL_3 0x0303 +#define OV13858_REG_PLL1_CTRL_4 0x0304 +#define OV13858_REG_PLL1_CTRL_5 0x0305 + +/* PLL2 generates DAC_CLK, SCLK and SRAM_CLK */ +#define OV13858_REG_PLL2_CTRL_B 0x030b +#define OV13858_REG_PLL2_CTRL_C 0x030c +#define OV13858_REG_PLL2_CTRL_D 0x030d +#define OV13858_REG_PLL2_CTRL_E 0x030e +#define OV13858_REG_PLL2_CTRL_F 0x030f +#define OV13858_REG_PLL2_CTRL_12 0x0312 +#define OV13858_REG_MIPI_SC_CTRL0 0x3016 +#define OV13858_REG_MIPI_SC_CTRL1 0x3022 + +/* Chip ID */ +#define OV13858_REG_CHIP_ID 0x300a +#define OV13858_CHIP_ID 0x00d855 + +/* V_TIMING internal */ +#define OV13858_REG_VTS 0x380e +#define OV13858_VTS_30FPS 0x0c8e /* 30 fps */ +#define OV13858_VTS_60FPS 0x0648 /* 60 fps */ +#define OV13858_VTS_MAX 0x7fff +#define OV13858_VBLANK_MIN 56 + +/* HBLANK control - read only */ +#define OV13858_PPL_540MHZ 2244 +#define OV13858_PPL_1080MHZ 4488 + +/* Exposure control */ +#define OV13858_REG_EXPOSURE 0x3500 +#define OV13858_EXPOSURE_MIN 4 +#define OV13858_EXPOSURE_MAX (OV13858_VTS_MAX - 8) +#define OV13858_EXPOSURE_STEP 1 +#define OV13858_EXPOSURE_DEFAULT 0x640 + +/* Analog gain control */ +#define OV13858_REG_ANALOG_GAIN 0x3508 +#define OV13858_ANA_GAIN_MIN 0 +#define OV13858_ANA_GAIN_MAX 0x1fff +#define OV13858_ANA_GAIN_STEP 1 +#define OV13858_ANA_GAIN_DEFAULT 0x80 + +/* Digital gain control */ +#define OV13858_REG_DIGITAL_GAIN 0x350a +#define OV13858_DGTL_GAIN_MASK 0xf3 +#define OV13858_DGTL_GAIN_SHIFT 2 +#define OV13858_DGTL_GAIN_MIN 1 +#define OV13858_DGTL_GAIN_MAX 4 +#define OV13858_DGTL_GAIN_STEP 1 +#define OV13858_DGTL_GAIN_DEFAULT 1 + +/* Test Pattern Control */ +#define OV13858_REG_TEST_PATTERN 0x4503 +#define OV13858_TEST_PATTERN_ENABLE BIT(7) +#define OV13858_TEST_PATTERN_MASK 0xfc + +/* Number of frames to skip */ +#define OV13858_NUM_OF_SKIP_FRAMES 2 + +struct ov13858_reg { + u16 address; + u8 val; +}; + +struct ov13858_reg_list { + u32 num_of_regs; + const struct ov13858_reg *regs; +}; + +/* Link frequency config */ +struct ov13858_link_freq_config { + u32 pixel_rate; + u32 pixels_per_line; + + /* PLL registers for this link frequency */ + struct ov13858_reg_list reg_list; +}; + +/* Mode : resolution and related config&values */ +struct ov13858_mode { + /* Frame width */ + u32 width; + /* Frame height */ + u32 height; + + /* V-timing */ + u32 vts; + + /* Index of Link frequency config to be used */ + u32 link_freq_index; + /* Default register values */ + struct ov13858_reg_list reg_list; +}; + +/* 4224x3136 needs 1080Mbps/lane, 4 lanes */ +static const struct ov13858_reg mipi_data_rate_1080mbps[] = { + /* PLL1 registers */ + {OV13858_REG_PLL1_CTRL_0, 0x07}, + {OV13858_REG_PLL1_CTRL_1, 0x01}, + {OV13858_REG_PLL1_CTRL_2, 0xc2}, + {OV13858_REG_PLL1_CTRL_3, 0x00}, + {OV13858_REG_PLL1_CTRL_4, 0x00}, + {OV13858_REG_PLL1_CTRL_5, 0x01}, + + /* PLL2 registers */ + {OV13858_REG_PLL2_CTRL_B, 0x05}, + {OV13858_REG_PLL2_CTRL_C, 0x01}, + {OV13858_REG_PLL2_CTRL_D, 0x0e}, + {OV13858_REG_PLL2_CTRL_E, 0x05}, + {OV13858_REG_PLL2_CTRL_F, 0x01}, + {OV13858_REG_PLL2_CTRL_12, 0x01}, + {OV13858_REG_MIPI_SC_CTRL0, 0x72}, + {OV13858_REG_MIPI_SC_CTRL1, 0x01}, +}; + +/* + * 2112x1568, 2112x1188, 1056x784 need 540Mbps/lane, + * 4 lanes + */ +static const struct ov13858_reg mipi_data_rate_540mbps[] = { + /* PLL1 registers */ + {OV13858_REG_PLL1_CTRL_0, 0x07}, + {OV13858_REG_PLL1_CTRL_1, 0x01}, + {OV13858_REG_PLL1_CTRL_2, 0xc2}, + {OV13858_REG_PLL1_CTRL_3, 0x01}, + {OV13858_REG_PLL1_CTRL_4, 0x00}, + {OV13858_REG_PLL1_CTRL_5, 0x01}, + + /* PLL2 registers */ + {OV13858_REG_PLL2_CTRL_B, 0x05}, + {OV13858_REG_PLL2_CTRL_C, 0x01}, + {OV13858_REG_PLL2_CTRL_D, 0x0e}, + {OV13858_REG_PLL2_CTRL_E, 0x05}, + {OV13858_REG_PLL2_CTRL_F, 0x01}, + {OV13858_REG_PLL2_CTRL_12, 0x01}, + {OV13858_REG_MIPI_SC_CTRL0, 0x72}, + {OV13858_REG_MIPI_SC_CTRL1, 0x01}, +}; + +static const struct ov13858_reg mode_4224x3136_regs[] = { + {0x3013, 0x32}, + {0x301b, 0xf0}, + {0x301f, 0xd0}, + {0x3106, 0x15}, + {0x3107, 0x23}, + {0x350a, 0x00}, + {0x350e, 0x00}, + {0x3510, 0x00}, + {0x3511, 0x02}, + {0x3512, 0x00}, + {0x3600, 0x2b}, + {0x3601, 0x52}, + {0x3602, 0x60}, + {0x3612, 0x05}, + {0x3613, 0xa4}, + {0x3620, 0x80}, + {0x3621, 0x10}, + {0x3622, 0x30}, + {0x3624, 0x1c}, + {0x3640, 0x10}, + {0x3641, 0x70}, + {0x3661, 0x80}, + {0x3662, 0x12}, + {0x3664, 0x73}, + {0x3665, 0xa7}, + {0x366e, 0xff}, + {0x366f, 0xf4}, + {0x3674, 0x00}, + {0x3679, 0x0c}, + {0x367f, 0x01}, + {0x3680, 0x0c}, + {0x3681, 0x50}, + {0x3682, 0x50}, + {0x3683, 0xa9}, + {0x3684, 0xa9}, + {0x3709, 0x5f}, + {0x3714, 0x24}, + {0x371a, 0x3e}, + {0x3737, 0x04}, + {0x3738, 0xcc}, + {0x3739, 0x12}, + {0x373d, 0x26}, + {0x3764, 0x20}, + {0x3765, 0x20}, + {0x37a1, 0x36}, + {0x37a8, 0x3b}, + {0x37ab, 0x31}, + {0x37c2, 0x04}, + {0x37c3, 0xf1}, + {0x37c5, 0x00}, + {0x37d8, 0x03}, + {0x37d9, 0x0c}, + {0x37da, 0xc2}, + {0x37dc, 0x02}, + {0x37e0, 0x00}, + {0x37e1, 0x0a}, + {0x37e2, 0x14}, + {0x37e3, 0x04}, + {0x37e4, 0x2a}, + {0x37e5, 0x03}, + {0x37e6, 0x04}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x00}, + {0x3804, 0x10}, + {0x3805, 0x9f}, + {0x3806, 0x0c}, + {0x3807, 0x5f}, + {0x3808, 0x10}, + {0x3809, 0x80}, + {0x380a, 0x0c}, + {0x380b, 0x40}, + {0x380c, 0x04}, + {0x380d, 0x62}, + {0x380e, 0x0c}, + {0x380f, 0x8e}, + {0x3811, 0x04}, + {0x3813, 0x05}, + {0x3814, 0x01}, + {0x3815, 0x01}, + {0x3816, 0x01}, + {0x3817, 0x01}, + {0x3820, 0xa8}, + {0x3821, 0x00}, + {0x3822, 0xc2}, + {0x3823, 0x18}, + {0x3826, 0x11}, + {0x3827, 0x1c}, + {0x3829, 0x03}, + {0x3832, 0x00}, + {0x3c80, 0x00}, + {0x3c87, 0x01}, + {0x3c8c, 0x19}, + {0x3c8d, 0x1c}, + {0x3c90, 0x00}, + {0x3c91, 0x00}, + {0x3c92, 0x00}, + {0x3c93, 0x00}, + {0x3c94, 0x40}, + {0x3c95, 0x54}, + {0x3c96, 0x34}, + {0x3c97, 0x04}, + {0x3c98, 0x00}, + {0x3d8c, 0x73}, + {0x3d8d, 0xc0}, + {0x3f00, 0x0b}, + {0x3f03, 0x00}, + {0x4001, 0xe0}, + {0x4008, 0x00}, + {0x4009, 0x0f}, + {0x4011, 0xf0}, + {0x4017, 0x08}, + {0x4050, 0x04}, + {0x4051, 0x0b}, + {0x4052, 0x00}, + {0x4053, 0x80}, + {0x4054, 0x00}, + {0x4055, 0x80}, + {0x4056, 0x00}, + {0x4057, 0x80}, + {0x4058, 0x00}, + {0x4059, 0x80}, + {0x405e, 0x20}, + {0x4500, 0x07}, + {0x4503, 0x00}, + {0x450a, 0x04}, + {0x4809, 0x04}, + {0x480c, 0x12}, + {0x481f, 0x30}, + {0x4833, 0x10}, + {0x4837, 0x0e}, + {0x4902, 0x01}, + {0x4d00, 0x03}, + {0x4d01, 0xc9}, + {0x4d02, 0xbc}, + {0x4d03, 0xd7}, + {0x4d04, 0xf0}, + {0x4d05, 0xa2}, + {0x5000, 0xfd}, + {0x5001, 0x01}, + {0x5040, 0x39}, + {0x5041, 0x10}, + {0x5042, 0x10}, + {0x5043, 0x84}, + {0x5044, 0x62}, + {0x5180, 0x00}, + {0x5181, 0x10}, + {0x5182, 0x02}, + {0x5183, 0x0f}, + {0x5200, 0x1b}, + {0x520b, 0x07}, + {0x520c, 0x0f}, + {0x5300, 0x04}, + {0x5301, 0x0c}, + {0x5302, 0x0c}, + {0x5303, 0x0f}, + {0x5304, 0x00}, + {0x5305, 0x70}, + {0x5306, 0x00}, + {0x5307, 0x80}, + {0x5308, 0x00}, + {0x5309, 0xa5}, + {0x530a, 0x00}, + {0x530b, 0xd3}, + {0x530c, 0x00}, + {0x530d, 0xf0}, + {0x530e, 0x01}, + {0x530f, 0x10}, + {0x5310, 0x01}, + {0x5311, 0x20}, + {0x5312, 0x01}, + {0x5313, 0x20}, + {0x5314, 0x01}, + {0x5315, 0x20}, + {0x5316, 0x08}, + {0x5317, 0x08}, + {0x5318, 0x10}, + {0x5319, 0x88}, + {0x531a, 0x88}, + {0x531b, 0xa9}, + {0x531c, 0xaa}, + {0x531d, 0x0a}, + {0x5405, 0x02}, + {0x5406, 0x67}, + {0x5407, 0x01}, + {0x5408, 0x4a}, +}; + +static const struct ov13858_reg mode_2112x1568_regs[] = { + {0x3013, 0x32}, + {0x301b, 0xf0}, + {0x301f, 0xd0}, + {0x3106, 0x15}, + {0x3107, 0x23}, + {0x350a, 0x00}, + {0x350e, 0x00}, + {0x3510, 0x00}, + {0x3511, 0x02}, + {0x3512, 0x00}, + {0x3600, 0x2b}, + {0x3601, 0x52}, + {0x3602, 0x60}, + {0x3612, 0x05}, + {0x3613, 0xa4}, + {0x3620, 0x80}, + {0x3621, 0x10}, + {0x3622, 0x30}, + {0x3624, 0x1c}, + {0x3640, 0x10}, + {0x3641, 0x70}, + {0x3661, 0x80}, + {0x3662, 0x10}, + {0x3664, 0x73}, + {0x3665, 0xa7}, + {0x366e, 0xff}, + {0x366f, 0xf4}, + {0x3674, 0x00}, + {0x3679, 0x0c}, + {0x367f, 0x01}, + {0x3680, 0x0c}, + {0x3681, 0x50}, + {0x3682, 0x50}, + {0x3683, 0xa9}, + {0x3684, 0xa9}, + {0x3709, 0x5f}, + {0x3714, 0x28}, + {0x371a, 0x3e}, + {0x3737, 0x08}, + {0x3738, 0xcc}, + {0x3739, 0x20}, + {0x373d, 0x26}, + {0x3764, 0x20}, + {0x3765, 0x20}, + {0x37a1, 0x36}, + {0x37a8, 0x3b}, + {0x37ab, 0x31}, + {0x37c2, 0x14}, + {0x37c3, 0xf1}, + {0x37c5, 0x00}, + {0x37d8, 0x03}, + {0x37d9, 0x0c}, + {0x37da, 0xc2}, + {0x37dc, 0x02}, + {0x37e0, 0x00}, + {0x37e1, 0x0a}, + {0x37e2, 0x14}, + {0x37e3, 0x08}, + {0x37e4, 0x38}, + {0x37e5, 0x03}, + {0x37e6, 0x08}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x00}, + {0x3804, 0x10}, + {0x3805, 0x9f}, + {0x3806, 0x0c}, + {0x3807, 0x5f}, + {0x3808, 0x08}, + {0x3809, 0x40}, + {0x380a, 0x06}, + {0x380b, 0x20}, + {0x380c, 0x04}, + {0x380d, 0x62}, + {0x380e, 0x0c}, + {0x380f, 0x8e}, + {0x3811, 0x04}, + {0x3813, 0x05}, + {0x3814, 0x03}, + {0x3815, 0x01}, + {0x3816, 0x03}, + {0x3817, 0x01}, + {0x3820, 0xab}, + {0x3821, 0x00}, + {0x3822, 0xc2}, + {0x3823, 0x18}, + {0x3826, 0x04}, + {0x3827, 0x90}, + {0x3829, 0x07}, + {0x3832, 0x00}, + {0x3c80, 0x00}, + {0x3c87, 0x01}, + {0x3c8c, 0x19}, + {0x3c8d, 0x1c}, + {0x3c90, 0x00}, + {0x3c91, 0x00}, + {0x3c92, 0x00}, + {0x3c93, 0x00}, + {0x3c94, 0x40}, + {0x3c95, 0x54}, + {0x3c96, 0x34}, + {0x3c97, 0x04}, + {0x3c98, 0x00}, + {0x3d8c, 0x73}, + {0x3d8d, 0xc0}, + {0x3f00, 0x0b}, + {0x3f03, 0x00}, + {0x4001, 0xe0}, + {0x4008, 0x00}, + {0x4009, 0x0d}, + {0x4011, 0xf0}, + {0x4017, 0x08}, + {0x4050, 0x04}, + {0x4051, 0x0b}, + {0x4052, 0x00}, + {0x4053, 0x80}, + {0x4054, 0x00}, + {0x4055, 0x80}, + {0x4056, 0x00}, + {0x4057, 0x80}, + {0x4058, 0x00}, + {0x4059, 0x80}, + {0x405e, 0x20}, + {0x4500, 0x07}, + {0x4503, 0x00}, + {0x450a, 0x04}, + {0x4809, 0x04}, + {0x480c, 0x12}, + {0x481f, 0x30}, + {0x4833, 0x10}, + {0x4837, 0x1c}, + {0x4902, 0x01}, + {0x4d00, 0x03}, + {0x4d01, 0xc9}, + {0x4d02, 0xbc}, + {0x4d03, 0xd7}, + {0x4d04, 0xf0}, + {0x4d05, 0xa2}, + {0x5000, 0xfd}, + {0x5001, 0x01}, + {0x5040, 0x39}, + {0x5041, 0x10}, + {0x5042, 0x10}, + {0x5043, 0x84}, + {0x5044, 0x62}, + {0x5180, 0x00}, + {0x5181, 0x10}, + {0x5182, 0x02}, + {0x5183, 0x0f}, + {0x5200, 0x1b}, + {0x520b, 0x07}, + {0x520c, 0x0f}, + {0x5300, 0x04}, + {0x5301, 0x0c}, + {0x5302, 0x0c}, + {0x5303, 0x0f}, + {0x5304, 0x00}, + {0x5305, 0x70}, + {0x5306, 0x00}, + {0x5307, 0x80}, + {0x5308, 0x00}, + {0x5309, 0xa5}, + {0x530a, 0x00}, + {0x530b, 0xd3}, + {0x530c, 0x00}, + {0x530d, 0xf0}, + {0x530e, 0x01}, + {0x530f, 0x10}, + {0x5310, 0x01}, + {0x5311, 0x20}, + {0x5312, 0x01}, + {0x5313, 0x20}, + {0x5314, 0x01}, + {0x5315, 0x20}, + {0x5316, 0x08}, + {0x5317, 0x08}, + {0x5318, 0x10}, + {0x5319, 0x88}, + {0x531a, 0x88}, + {0x531b, 0xa9}, + {0x531c, 0xaa}, + {0x531d, 0x0a}, + {0x5405, 0x02}, + {0x5406, 0x67}, + {0x5407, 0x01}, + {0x5408, 0x4a}, +}; + +static const struct ov13858_reg mode_2112x1188_regs[] = { + {0x3013, 0x32}, + {0x301b, 0xf0}, + {0x301f, 0xd0}, + {0x3106, 0x15}, + {0x3107, 0x23}, + {0x350a, 0x00}, + {0x350e, 0x00}, + {0x3510, 0x00}, + {0x3511, 0x02}, + {0x3512, 0x00}, + {0x3600, 0x2b}, + {0x3601, 0x52}, + {0x3602, 0x60}, + {0x3612, 0x05}, + {0x3613, 0xa4}, + {0x3620, 0x80}, + {0x3621, 0x10}, + {0x3622, 0x30}, + {0x3624, 0x1c}, + {0x3640, 0x10}, + {0x3641, 0x70}, + {0x3661, 0x80}, + {0x3662, 0x10}, + {0x3664, 0x73}, + {0x3665, 0xa7}, + {0x366e, 0xff}, + {0x366f, 0xf4}, + {0x3674, 0x00}, + {0x3679, 0x0c}, + {0x367f, 0x01}, + {0x3680, 0x0c}, + {0x3681, 0x50}, + {0x3682, 0x50}, + {0x3683, 0xa9}, + {0x3684, 0xa9}, + {0x3709, 0x5f}, + {0x3714, 0x28}, + {0x371a, 0x3e}, + {0x3737, 0x08}, + {0x3738, 0xcc}, + {0x3739, 0x20}, + {0x373d, 0x26}, + {0x3764, 0x20}, + {0x3765, 0x20}, + {0x37a1, 0x36}, + {0x37a8, 0x3b}, + {0x37ab, 0x31}, + {0x37c2, 0x14}, + {0x37c3, 0xf1}, + {0x37c5, 0x00}, + {0x37d8, 0x03}, + {0x37d9, 0x0c}, + {0x37da, 0xc2}, + {0x37dc, 0x02}, + {0x37e0, 0x00}, + {0x37e1, 0x0a}, + {0x37e2, 0x14}, + {0x37e3, 0x08}, + {0x37e4, 0x38}, + {0x37e5, 0x03}, + {0x37e6, 0x08}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x01}, + {0x3803, 0x84}, + {0x3804, 0x10}, + {0x3805, 0x9f}, + {0x3806, 0x0a}, + {0x3807, 0xd3}, + {0x3808, 0x08}, + {0x3809, 0x40}, + {0x380a, 0x04}, + {0x380b, 0xa4}, + {0x380c, 0x04}, + {0x380d, 0x62}, + {0x380e, 0x0c}, + {0x380f, 0x8e}, + {0x3811, 0x08}, + {0x3813, 0x03}, + {0x3814, 0x03}, + {0x3815, 0x01}, + {0x3816, 0x03}, + {0x3817, 0x01}, + {0x3820, 0xab}, + {0x3821, 0x00}, + {0x3822, 0xc2}, + {0x3823, 0x18}, + {0x3826, 0x04}, + {0x3827, 0x90}, + {0x3829, 0x07}, + {0x3832, 0x00}, + {0x3c80, 0x00}, + {0x3c87, 0x01}, + {0x3c8c, 0x19}, + {0x3c8d, 0x1c}, + {0x3c90, 0x00}, + {0x3c91, 0x00}, + {0x3c92, 0x00}, + {0x3c93, 0x00}, + {0x3c94, 0x40}, + {0x3c95, 0x54}, + {0x3c96, 0x34}, + {0x3c97, 0x04}, + {0x3c98, 0x00}, + {0x3d8c, 0x73}, + {0x3d8d, 0xc0}, + {0x3f00, 0x0b}, + {0x3f03, 0x00}, + {0x4001, 0xe0}, + {0x4008, 0x00}, + {0x4009, 0x0d}, + {0x4011, 0xf0}, + {0x4017, 0x08}, + {0x4050, 0x04}, + {0x4051, 0x0b}, + {0x4052, 0x00}, + {0x4053, 0x80}, + {0x4054, 0x00}, + {0x4055, 0x80}, + {0x4056, 0x00}, + {0x4057, 0x80}, + {0x4058, 0x00}, + {0x4059, 0x80}, + {0x405e, 0x20}, + {0x4500, 0x07}, + {0x4503, 0x00}, + {0x450a, 0x04}, + {0x4809, 0x04}, + {0x480c, 0x12}, + {0x481f, 0x30}, + {0x4833, 0x10}, + {0x4837, 0x1c}, + {0x4902, 0x01}, + {0x4d00, 0x03}, + {0x4d01, 0xc9}, + {0x4d02, 0xbc}, + {0x4d03, 0xd7}, + {0x4d04, 0xf0}, + {0x4d05, 0xa2}, + {0x5000, 0xfd}, + {0x5001, 0x01}, + {0x5040, 0x39}, + {0x5041, 0x10}, + {0x5042, 0x10}, + {0x5043, 0x84}, + {0x5044, 0x62}, + {0x5180, 0x00}, + {0x5181, 0x10}, + {0x5182, 0x02}, + {0x5183, 0x0f}, + {0x5200, 0x1b}, + {0x520b, 0x07}, + {0x520c, 0x0f}, + {0x5300, 0x04}, + {0x5301, 0x0c}, + {0x5302, 0x0c}, + {0x5303, 0x0f}, + {0x5304, 0x00}, + {0x5305, 0x70}, + {0x5306, 0x00}, + {0x5307, 0x80}, + {0x5308, 0x00}, + {0x5309, 0xa5}, + {0x530a, 0x00}, + {0x530b, 0xd3}, + {0x530c, 0x00}, + {0x530d, 0xf0}, + {0x530e, 0x01}, + {0x530f, 0x10}, + {0x5310, 0x01}, + {0x5311, 0x20}, + {0x5312, 0x01}, + {0x5313, 0x20}, + {0x5314, 0x01}, + {0x5315, 0x20}, + {0x5316, 0x08}, + {0x5317, 0x08}, + {0x5318, 0x10}, + {0x5319, 0x88}, + {0x531a, 0x88}, + {0x531b, 0xa9}, + {0x531c, 0xaa}, + {0x531d, 0x0a}, + {0x5405, 0x02}, + {0x5406, 0x67}, + {0x5407, 0x01}, + {0x5408, 0x4a}, +}; + +static const struct ov13858_reg mode_1056x784_regs[] = { + {0x3013, 0x32}, + {0x301b, 0xf0}, + {0x301f, 0xd0}, + {0x3106, 0x15}, + {0x3107, 0x23}, + {0x350a, 0x00}, + {0x350e, 0x00}, + {0x3510, 0x00}, + {0x3511, 0x02}, + {0x3512, 0x00}, + {0x3600, 0x2b}, + {0x3601, 0x52}, + {0x3602, 0x60}, + {0x3612, 0x05}, + {0x3613, 0xa4}, + {0x3620, 0x80}, + {0x3621, 0x10}, + {0x3622, 0x30}, + {0x3624, 0x1c}, + {0x3640, 0x10}, + {0x3641, 0x70}, + {0x3661, 0x80}, + {0x3662, 0x08}, + {0x3664, 0x73}, + {0x3665, 0xa7}, + {0x366e, 0xff}, + {0x366f, 0xf4}, + {0x3674, 0x00}, + {0x3679, 0x0c}, + {0x367f, 0x01}, + {0x3680, 0x0c}, + {0x3681, 0x50}, + {0x3682, 0x50}, + {0x3683, 0xa9}, + {0x3684, 0xa9}, + {0x3709, 0x5f}, + {0x3714, 0x30}, + {0x371a, 0x3e}, + {0x3737, 0x08}, + {0x3738, 0xcc}, + {0x3739, 0x20}, + {0x373d, 0x26}, + {0x3764, 0x20}, + {0x3765, 0x20}, + {0x37a1, 0x36}, + {0x37a8, 0x3b}, + {0x37ab, 0x31}, + {0x37c2, 0x2c}, + {0x37c3, 0xf1}, + {0x37c5, 0x00}, + {0x37d8, 0x03}, + {0x37d9, 0x06}, + {0x37da, 0xc2}, + {0x37dc, 0x02}, + {0x37e0, 0x00}, + {0x37e1, 0x0a}, + {0x37e2, 0x14}, + {0x37e3, 0x08}, + {0x37e4, 0x36}, + {0x37e5, 0x03}, + {0x37e6, 0x08}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x00}, + {0x3804, 0x10}, + {0x3805, 0x9f}, + {0x3806, 0x0c}, + {0x3807, 0x5f}, + {0x3808, 0x04}, + {0x3809, 0x20}, + {0x380a, 0x03}, + {0x380b, 0x10}, + {0x380c, 0x04}, + {0x380d, 0x62}, + {0x380e, 0x0c}, + {0x380f, 0x8e}, + {0x3811, 0x04}, + {0x3813, 0x05}, + {0x3814, 0x07}, + {0x3815, 0x01}, + {0x3816, 0x07}, + {0x3817, 0x01}, + {0x3820, 0xac}, + {0x3821, 0x00}, + {0x3822, 0xc2}, + {0x3823, 0x18}, + {0x3826, 0x04}, + {0x3827, 0x48}, + {0x3829, 0x03}, + {0x3832, 0x00}, + {0x3c80, 0x00}, + {0x3c87, 0x01}, + {0x3c8c, 0x19}, + {0x3c8d, 0x1c}, + {0x3c90, 0x00}, + {0x3c91, 0x00}, + {0x3c92, 0x00}, + {0x3c93, 0x00}, + {0x3c94, 0x40}, + {0x3c95, 0x54}, + {0x3c96, 0x34}, + {0x3c97, 0x04}, + {0x3c98, 0x00}, + {0x3d8c, 0x73}, + {0x3d8d, 0xc0}, + {0x3f00, 0x0b}, + {0x3f03, 0x00}, + {0x4001, 0xe0}, + {0x4008, 0x00}, + {0x4009, 0x05}, + {0x4011, 0xf0}, + {0x4017, 0x08}, + {0x4050, 0x02}, + {0x4051, 0x05}, + {0x4052, 0x00}, + {0x4053, 0x80}, + {0x4054, 0x00}, + {0x4055, 0x80}, + {0x4056, 0x00}, + {0x4057, 0x80}, + {0x4058, 0x00}, + {0x4059, 0x80}, + {0x405e, 0x20}, + {0x4500, 0x07}, + {0x4503, 0x00}, + {0x450a, 0x04}, + {0x4809, 0x04}, + {0x480c, 0x12}, + {0x481f, 0x30}, + {0x4833, 0x10}, + {0x4837, 0x1e}, + {0x4902, 0x02}, + {0x4d00, 0x03}, + {0x4d01, 0xc9}, + {0x4d02, 0xbc}, + {0x4d03, 0xd7}, + {0x4d04, 0xf0}, + {0x4d05, 0xa2}, + {0x5000, 0xfd}, + {0x5001, 0x01}, + {0x5040, 0x39}, + {0x5041, 0x10}, + {0x5042, 0x10}, + {0x5043, 0x84}, + {0x5044, 0x62}, + {0x5180, 0x00}, + {0x5181, 0x10}, + {0x5182, 0x02}, + {0x5183, 0x0f}, + {0x5200, 0x1b}, + {0x520b, 0x07}, + {0x520c, 0x0f}, + {0x5300, 0x04}, + {0x5301, 0x0c}, + {0x5302, 0x0c}, + {0x5303, 0x0f}, + {0x5304, 0x00}, + {0x5305, 0x70}, + {0x5306, 0x00}, + {0x5307, 0x80}, + {0x5308, 0x00}, + {0x5309, 0xa5}, + {0x530a, 0x00}, + {0x530b, 0xd3}, + {0x530c, 0x00}, + {0x530d, 0xf0}, + {0x530e, 0x01}, + {0x530f, 0x10}, + {0x5310, 0x01}, + {0x5311, 0x20}, + {0x5312, 0x01}, + {0x5313, 0x20}, + {0x5314, 0x01}, + {0x5315, 0x20}, + {0x5316, 0x08}, + {0x5317, 0x08}, + {0x5318, 0x10}, + {0x5319, 0x88}, + {0x531a, 0x88}, + {0x531b, 0xa9}, + {0x531c, 0xaa}, + {0x531d, 0x0a}, + {0x5405, 0x02}, + {0x5406, 0x67}, + {0x5407, 0x01}, + {0x5408, 0x4a}, +}; + +static const char * const ov13858_test_pattern_menu[] = { + "Disabled", + "Vertical Color Bar Type 1", + "Vertical Color Bar Type 2", + "Vertical Color Bar Type 3", + "Vertical Color Bar Type 4" +}; + +/* Configurations for supported link frequencies */ +#define OV13858_NUM_OF_LINK_FREQS 2 +#define OV13858_LINK_FREQ_1080MBPS 1080000000 +#define OV13858_LINK_FREQ_540MBPS 540000000 +#define OV13858_LINK_FREQ_INDEX_0 0 +#define OV13858_LINK_FREQ_INDEX_1 1 + +/* Menu items for LINK_FREQ V4L2 control */ +static const s64 link_freq_menu_items[OV13858_NUM_OF_LINK_FREQS] = { + OV13858_LINK_FREQ_1080MBPS, + OV13858_LINK_FREQ_540MBPS +}; + +/* Link frequency configs */ +static const struct ov13858_link_freq_config + link_freq_configs[OV13858_NUM_OF_LINK_FREQS] = { + { + .pixel_rate = 864000000, + .pixels_per_line = OV13858_PPL_1080MHZ, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mipi_data_rate_1080mbps), + .regs = mipi_data_rate_1080mbps, + } + }, + { + .pixel_rate = 432000000, + .pixels_per_line = OV13858_PPL_540MHZ, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mipi_data_rate_540mbps), + .regs = mipi_data_rate_540mbps, + } + } +}; + +/* Mode configs */ +static const struct ov13858_mode supported_modes[] = { + { + .width = 4224, + .height = 3136, + .vts = OV13858_VTS_30FPS, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_4224x3136_regs), + .regs = mode_4224x3136_regs, + }, + .link_freq_index = OV13858_LINK_FREQ_INDEX_0, + }, + { + .width = 2112, + .height = 1568, + .vts = OV13858_VTS_30FPS, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_2112x1568_regs), + .regs = mode_2112x1568_regs, + }, + .link_freq_index = OV13858_LINK_FREQ_INDEX_1, + }, + { + .width = 2112, + .height = 1188, + .vts = OV13858_VTS_30FPS, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_2112x1188_regs), + .regs = mode_2112x1188_regs, + }, + .link_freq_index = OV13858_LINK_FREQ_INDEX_1, + }, + { + .width = 1056, + .height = 784, + .vts = OV13858_VTS_30FPS, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1056x784_regs), + .regs = mode_1056x784_regs, + }, + .link_freq_index = OV13858_LINK_FREQ_INDEX_1, + } +}; + +struct ov13858 { + struct v4l2_subdev sd; + struct media_pad pad; + + struct v4l2_ctrl_handler ctrl_handler; + /* V4L2 Controls */ + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *exposure; + + /* Current mode */ + const struct ov13858_mode *cur_mode; + + /* Mutex for serialized access */ + struct mutex mutex; + + /* Streaming on/off */ + bool streaming; +}; + +#define to_ov13858(_sd) container_of(_sd, struct ov13858, sd) + +/* Read registers up to 4 at a time */ +static int ov13858_read_reg(struct ov13858 *ov13858, u16 reg, u32 len, u32 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov13858->sd); + struct i2c_msg msgs[2]; + u8 *data_be_p; + int ret; + u32 data_be = 0; + u16 reg_addr_be = cpu_to_be16(reg); + + if (len > 4) + return -EINVAL; + + data_be_p = (u8 *)&data_be; + /* Write register address */ + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = 2; + msgs[0].buf = (u8 *)®_addr_be; + + /* Read data from register */ + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = &data_be_p[4 - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = be32_to_cpu(data_be); + + return 0; +} + +/* Write registers up to 4 at a time */ +static int ov13858_write_reg(struct ov13858 *ov13858, u16 reg, u32 len, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov13858->sd); + int buf_i, val_i; + u8 buf[6], *val_p; + + if (len > 4) + return -EINVAL; + + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + + val = cpu_to_be32(val); + val_p = (u8 *)&val; + buf_i = 2; + val_i = 4 - len; + + while (val_i < 4) + buf[buf_i++] = val_p[val_i++]; + + if (i2c_master_send(client, buf, len + 2) != len + 2) + return -EIO; + + return 0; +} + +/* Write a list of registers */ +static int ov13858_write_regs(struct ov13858 *ov13858, + const struct ov13858_reg *regs, u32 len) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov13858->sd); + int ret; + u32 i; + + for (i = 0; i < len; i++) { + ret = ov13858_write_reg(ov13858, regs[i].address, 1, + regs[i].val); + if (ret) { + dev_err_ratelimited( + &client->dev, + "Failed to write reg 0x%4.4x. error = %d\n", + regs[i].address, ret); + + return ret; + } + } + + return 0; +} + +static int ov13858_write_reg_list(struct ov13858 *ov13858, + const struct ov13858_reg_list *r_list) +{ + return ov13858_write_regs(ov13858, r_list->regs, r_list->num_of_regs); +} + +/* Open sub-device */ +static int ov13858_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct ov13858 *ov13858 = to_ov13858(sd); + struct v4l2_mbus_framefmt *try_fmt = v4l2_subdev_get_try_format(sd, + fh->pad, + 0); + + mutex_lock(&ov13858->mutex); + + /* Initialize try_fmt */ + try_fmt->width = ov13858->cur_mode->width; + try_fmt->height = ov13858->cur_mode->height; + try_fmt->code = MEDIA_BUS_FMT_SGRBG10_1X10; + try_fmt->field = V4L2_FIELD_NONE; + + /* No crop or compose */ + mutex_unlock(&ov13858->mutex); + + return 0; +} + +static int ov13858_update_digital_gain(struct ov13858 *ov13858, u32 d_gain) +{ + int ret; + u32 val; + + if (d_gain == 3) + return -EINVAL; + + ret = ov13858_read_reg(ov13858, OV13858_REG_DIGITAL_GAIN, + OV13858_REG_VALUE_08BIT, &val); + if (ret) + return ret; + + val &= OV13858_DGTL_GAIN_MASK; + val |= (d_gain - 1) << OV13858_DGTL_GAIN_SHIFT; + + return ov13858_write_reg(ov13858, OV13858_REG_DIGITAL_GAIN, + OV13858_REG_VALUE_08BIT, val); +} + +static int ov13858_enable_test_pattern(struct ov13858 *ov13858, u32 pattern) +{ + int ret; + u32 val; + + ret = ov13858_read_reg(ov13858, OV13858_REG_TEST_PATTERN, + OV13858_REG_VALUE_08BIT, &val); + if (ret) + return ret; + + if (pattern) { + val &= OV13858_TEST_PATTERN_MASK; + val |= (pattern - 1) | OV13858_TEST_PATTERN_ENABLE; + } else { + val &= ~OV13858_TEST_PATTERN_ENABLE; + } + + return ov13858_write_reg(ov13858, OV13858_REG_TEST_PATTERN, + OV13858_REG_VALUE_08BIT, val); +} + +static int ov13858_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ov13858 *ov13858 = container_of(ctrl->handler, + struct ov13858, ctrl_handler); + struct i2c_client *client = v4l2_get_subdevdata(&ov13858->sd); + s64 max; + int ret; + + /* Propagate change of current control to all related controls */ + switch (ctrl->id) { + case V4L2_CID_VBLANK: + /* Update max exposure while meeting expected vblanking */ + max = ov13858->cur_mode->height + ctrl->val - 8; + __v4l2_ctrl_modify_range(ov13858->exposure, + ov13858->exposure->minimum, + max, ov13858->exposure->step, max); + break; + }; + + /* + * Applying V4L2 control value only happens + * when power is up for streaming + */ + if (pm_runtime_get_if_in_use(&client->dev) <= 0) + return 0; + + ret = 0; + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + ret = ov13858_write_reg(ov13858, OV13858_REG_ANALOG_GAIN, + OV13858_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_DIGITAL_GAIN: + ret = ov13858_update_digital_gain(ov13858, ctrl->val); + break; + case V4L2_CID_EXPOSURE: + ret = ov13858_write_reg(ov13858, OV13858_REG_EXPOSURE, + OV13858_REG_VALUE_24BIT, + ctrl->val << 4); + break; + case V4L2_CID_VBLANK: + /* Update VTS that meets expected vertical blanking */ + ret = ov13858_write_reg(ov13858, OV13858_REG_VTS, + OV13858_REG_VALUE_16BIT, + ov13858->cur_mode->height + + ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: + ret = ov13858_enable_test_pattern(ov13858, ctrl->val); + break; + default: + dev_info(&client->dev, + "ctrl(id:0x%x,val:0x%x) is not handled\n", + ctrl->id, ctrl->val); + break; + }; + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops ov13858_ctrl_ops = { + .s_ctrl = ov13858_set_ctrl, +}; + +static int ov13858_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + /* Only one bayer order(GRBG) is supported */ + if (code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_SGRBG10_1X10; + + return 0; +} + +static int ov13858_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10) + return -EINVAL; + + fse->min_width = supported_modes[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = supported_modes[fse->index].height; + fse->max_height = fse->min_height; + + return 0; +} + +static void ov13858_update_pad_format(const struct ov13858_mode *mode, + struct v4l2_subdev_format *fmt) +{ + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10; + fmt->format.field = V4L2_FIELD_NONE; +} + +static int ov13858_do_get_pad_format(struct ov13858 *ov13858, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *framefmt; + struct v4l2_subdev *sd = &ov13858->sd; + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + framefmt = v4l2_subdev_get_try_format(sd, cfg, fmt->pad); + fmt->format = *framefmt; + } else { + ov13858_update_pad_format(ov13858->cur_mode, fmt); + } + + return 0; +} + +static int ov13858_get_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct ov13858 *ov13858 = to_ov13858(sd); + int ret; + + mutex_lock(&ov13858->mutex); + ret = ov13858_do_get_pad_format(ov13858, cfg, fmt); + mutex_unlock(&ov13858->mutex); + + return ret; +} + +/* + * Calculate resolution distance + */ +static int +ov13858_get_resolution_dist(const struct ov13858_mode *mode, + struct v4l2_mbus_framefmt *framefmt) +{ + return abs(mode->width - framefmt->width) + + abs(mode->height - framefmt->height); +} + +/* + * Find the closest supported resolution to the requested resolution + */ +static const struct ov13858_mode * +ov13858_find_best_fit(struct ov13858 *ov13858, + struct v4l2_subdev_format *fmt) +{ + int i, dist, cur_best_fit = 0, cur_best_fit_dist = -1; + struct v4l2_mbus_framefmt *framefmt = &fmt->format; + + for (i = 0; i < ARRAY_SIZE(supported_modes); i++) { + dist = ov13858_get_resolution_dist(&supported_modes[i], + framefmt); + if (cur_best_fit_dist == -1 || dist < cur_best_fit_dist) { + cur_best_fit_dist = dist; + cur_best_fit = i; + } + } + + return &supported_modes[cur_best_fit]; +} + +static int +ov13858_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct ov13858 *ov13858 = to_ov13858(sd); + const struct ov13858_mode *mode; + struct v4l2_mbus_framefmt *framefmt; + s64 h_blank; + + mutex_lock(&ov13858->mutex); + + /* Only one raw bayer(GRBG) order is supported */ + if (fmt->format.code != MEDIA_BUS_FMT_SGRBG10_1X10) + fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10; + + mode = ov13858_find_best_fit(ov13858, fmt); + ov13858_update_pad_format(mode, fmt); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + framefmt = v4l2_subdev_get_try_format(sd, cfg, fmt->pad); + *framefmt = fmt->format; + } else { + ov13858->cur_mode = mode; + __v4l2_ctrl_s_ctrl(ov13858->link_freq, mode->link_freq_index); + __v4l2_ctrl_s_ctrl_int64( + ov13858->pixel_rate, + link_freq_configs[mode->link_freq_index].pixel_rate); + /* Update limits and set FPS to default */ + __v4l2_ctrl_modify_range( + ov13858->vblank, OV13858_VBLANK_MIN, + OV13858_VTS_MAX - ov13858->cur_mode->height, 1, + ov13858->cur_mode->vts - ov13858->cur_mode->height); + h_blank = + link_freq_configs[mode->link_freq_index].pixels_per_line + - ov13858->cur_mode->width; + __v4l2_ctrl_modify_range(ov13858->hblank, h_blank, + h_blank, 1, h_blank); + } + + mutex_unlock(&ov13858->mutex); + + return 0; +} + +static int ov13858_get_skip_frames(struct v4l2_subdev *sd, u32 *frames) +{ + *frames = OV13858_NUM_OF_SKIP_FRAMES; + + return 0; +} + +/* Start streaming */ +static int ov13858_start_streaming(struct ov13858 *ov13858) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov13858->sd); + const struct ov13858_reg_list *reg_list; + int ret, link_freq_index; + + /* Get out of from software reset */ + ret = ov13858_write_reg(ov13858, OV13858_REG_SOFTWARE_RST, + OV13858_REG_VALUE_08BIT, OV13858_SOFTWARE_RST); + if (ret) { + dev_err(&client->dev, "%s failed to set powerup registers\n", + __func__); + return ret; + } + + /* Setup PLL */ + link_freq_index = ov13858->cur_mode->link_freq_index; + reg_list = &link_freq_configs[link_freq_index].reg_list; + ret = ov13858_write_reg_list(ov13858, reg_list); + if (ret) { + dev_err(&client->dev, "%s failed to set plls\n", __func__); + return ret; + } + + /* Apply default values of current mode */ + reg_list = &ov13858->cur_mode->reg_list; + ret = ov13858_write_reg_list(ov13858, reg_list); + if (ret) { + dev_err(&client->dev, "%s failed to set mode\n", __func__); + return ret; + } + + /* Apply customized values from user */ + ret = __v4l2_ctrl_handler_setup(ov13858->sd.ctrl_handler); + if (ret) + return ret; + + return ov13858_write_reg(ov13858, OV13858_REG_MODE_SELECT, + OV13858_REG_VALUE_08BIT, + OV13858_MODE_STREAMING); +} + +/* Stop streaming */ +static int ov13858_stop_streaming(struct ov13858 *ov13858) +{ + return ov13858_write_reg(ov13858, OV13858_REG_MODE_SELECT, + OV13858_REG_VALUE_08BIT, OV13858_MODE_STANDBY); +} + +static int ov13858_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct ov13858 *ov13858 = to_ov13858(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret = 0; + + mutex_lock(&ov13858->mutex); + if (ov13858->streaming == enable) { + mutex_unlock(&ov13858->mutex); + return 0; + } + + if (enable) { + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_noidle(&client->dev); + goto err_unlock; + } + + /* + * Apply default & customized values + * and then start streaming. + */ + ret = ov13858_start_streaming(ov13858); + if (ret) + goto err_rpm_put; + } else { + ov13858_stop_streaming(ov13858); + pm_runtime_put(&client->dev); + } + + ov13858->streaming = enable; + mutex_unlock(&ov13858->mutex); + + return ret; + +err_rpm_put: + pm_runtime_put(&client->dev); +err_unlock: + mutex_unlock(&ov13858->mutex); + + return ret; +} + +static int __maybe_unused ov13858_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov13858 *ov13858 = to_ov13858(sd); + + if (ov13858->streaming) + ov13858_stop_streaming(ov13858); + + return 0; +} + +static int __maybe_unused ov13858_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov13858 *ov13858 = to_ov13858(sd); + int ret; + + if (ov13858->streaming) { + ret = ov13858_start_streaming(ov13858); + if (ret) + goto error; + } + + return 0; + +error: + ov13858_stop_streaming(ov13858); + ov13858->streaming = 0; + return ret; +} + +/* Verify chip ID */ +static int ov13858_identify_module(struct ov13858 *ov13858) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov13858->sd); + int ret; + u32 val; + + ret = ov13858_read_reg(ov13858, OV13858_REG_CHIP_ID, + OV13858_REG_VALUE_24BIT, &val); + if (ret) + return ret; + + if (val != OV13858_CHIP_ID) { + dev_err(&client->dev, "chip id mismatch: %x!=%x\n", + OV13858_CHIP_ID, val); + return -EIO; + } + + return 0; +} + +static const struct v4l2_subdev_video_ops ov13858_video_ops = { + .s_stream = ov13858_set_stream, +}; + +static const struct v4l2_subdev_pad_ops ov13858_pad_ops = { + .enum_mbus_code = ov13858_enum_mbus_code, + .get_fmt = ov13858_get_pad_format, + .set_fmt = ov13858_set_pad_format, + .enum_frame_size = ov13858_enum_frame_size, +}; + +static const struct v4l2_subdev_sensor_ops ov13858_sensor_ops = { + .g_skip_frames = ov13858_get_skip_frames, +}; + +static const struct v4l2_subdev_ops ov13858_subdev_ops = { + .video = &ov13858_video_ops, + .pad = &ov13858_pad_ops, + .sensor = &ov13858_sensor_ops, +}; + +static const struct media_entity_operations ov13858_subdev_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct v4l2_subdev_internal_ops ov13858_internal_ops = { + .open = ov13858_open, +}; + +/* Initialize control handlers */ +static int ov13858_init_controls(struct ov13858 *ov13858) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov13858->sd); + struct v4l2_ctrl_handler *ctrl_hdlr; + int ret; + + ctrl_hdlr = &ov13858->ctrl_handler; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 8); + if (ret) + return ret; + + mutex_init(&ov13858->mutex); + ctrl_hdlr->lock = &ov13858->mutex; + ov13858->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, + &ov13858_ctrl_ops, + V4L2_CID_LINK_FREQ, + OV13858_NUM_OF_LINK_FREQS - 1, + 0, + link_freq_menu_items); + ov13858->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + /* By default, PIXEL_RATE is read only */ + ov13858->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &ov13858_ctrl_ops, + V4L2_CID_PIXEL_RATE, 0, + link_freq_configs[0].pixel_rate, 1, + link_freq_configs[0].pixel_rate); + + ov13858->vblank = v4l2_ctrl_new_std( + ctrl_hdlr, &ov13858_ctrl_ops, V4L2_CID_VBLANK, + OV13858_VBLANK_MIN, + OV13858_VTS_MAX - ov13858->cur_mode->height, 1, + ov13858->cur_mode->vts + - ov13858->cur_mode->height); + + ov13858->hblank = v4l2_ctrl_new_std( + ctrl_hdlr, &ov13858_ctrl_ops, V4L2_CID_HBLANK, + OV13858_PPL_1080MHZ - ov13858->cur_mode->width, + OV13858_PPL_1080MHZ - ov13858->cur_mode->width, + 1, + OV13858_PPL_1080MHZ - ov13858->cur_mode->width); + ov13858->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + ov13858->exposure = v4l2_ctrl_new_std( + ctrl_hdlr, &ov13858_ctrl_ops, + V4L2_CID_EXPOSURE, OV13858_EXPOSURE_MIN, + OV13858_EXPOSURE_MAX, OV13858_EXPOSURE_STEP, + OV13858_EXPOSURE_DEFAULT); + + v4l2_ctrl_new_std(ctrl_hdlr, &ov13858_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + OV13858_ANA_GAIN_MIN, OV13858_ANA_GAIN_MAX, + OV13858_ANA_GAIN_STEP, OV13858_ANA_GAIN_DEFAULT); + + /* Digital gain */ + v4l2_ctrl_new_std(ctrl_hdlr, &ov13858_ctrl_ops, V4L2_CID_DIGITAL_GAIN, + OV13858_DGTL_GAIN_MIN, OV13858_DGTL_GAIN_MAX, + OV13858_DGTL_GAIN_STEP, OV13858_DGTL_GAIN_DEFAULT); + + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &ov13858_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ov13858_test_pattern_menu) - 1, + 0, 0, ov13858_test_pattern_menu); + if (ctrl_hdlr->error) { + ret = ctrl_hdlr->error; + dev_err(&client->dev, "%s control init failed (%d)\n", + __func__, ret); + goto error; + } + + ov13858->sd.ctrl_handler = ctrl_hdlr; + + return 0; + +error: + v4l2_ctrl_handler_free(ctrl_hdlr); + mutex_destroy(&ov13858->mutex); + + return ret; +} + +static void ov13858_free_controls(struct ov13858 *ov13858) +{ + v4l2_ctrl_handler_free(ov13858->sd.ctrl_handler); + mutex_destroy(&ov13858->mutex); +} + +static int ov13858_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct ov13858 *ov13858; + int ret; + u32 val = 0; + + device_property_read_u32(&client->dev, "clock-frequency", &val); + if (val != 19200000) + return -EINVAL; + + ov13858 = devm_kzalloc(&client->dev, sizeof(*ov13858), GFP_KERNEL); + if (!ov13858) + return -ENOMEM; + + /* Initialize subdev */ + v4l2_i2c_subdev_init(&ov13858->sd, client, &ov13858_subdev_ops); + + /* Check module identity */ + ret = ov13858_identify_module(ov13858); + if (ret) { + dev_err(&client->dev, "failed to find sensor: %d\n", ret); + return ret; + } + + /* Set default mode to max resolution */ + ov13858->cur_mode = &supported_modes[0]; + + ret = ov13858_init_controls(ov13858); + if (ret) + return ret; + + /* Initialize subdev */ + ov13858->sd.internal_ops = &ov13858_internal_ops; + ov13858->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + ov13858->sd.entity.ops = &ov13858_subdev_entity_ops; + ov13858->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + /* Initialize source pad */ + ov13858->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&ov13858->sd.entity, 1, &ov13858->pad); + if (ret) { + dev_err(&client->dev, "%s failed:%d\n", __func__, ret); + goto error_handler_free; + } + + ret = v4l2_async_register_subdev(&ov13858->sd); + if (ret < 0) + goto error_media_entity; + + /* + * Device is already turned on by i2c-core with ACPI domain PM. + * Enable runtime PM and turn off the device. + */ + pm_runtime_get_noresume(&client->dev); + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + pm_runtime_put(&client->dev); + + return 0; + +error_media_entity: + media_entity_cleanup(&ov13858->sd.entity); + +error_handler_free: + ov13858_free_controls(ov13858); + dev_err(&client->dev, "%s failed:%d\n", __func__, ret); + + return ret; +} + +static int ov13858_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov13858 *ov13858 = to_ov13858(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + ov13858_free_controls(ov13858); + + /* + * Disable runtime PM but keep the device turned on. + * i2c-core with ACPI domain PM will turn off the device. + */ + pm_runtime_get_sync(&client->dev); + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + pm_runtime_put_noidle(&client->dev); + + return 0; +} + +static const struct i2c_device_id ov13858_id_table[] = { + {"ov13858", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, ov13858_id_table); + +static const struct dev_pm_ops ov13858_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(ov13858_suspend, ov13858_resume) +}; + +#ifdef CONFIG_ACPI +static const struct acpi_device_id ov13858_acpi_ids[] = { + {"OVTID858"}, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(acpi, ov13858_acpi_ids); +#endif + +static struct i2c_driver ov13858_i2c_driver = { + .driver = { + .name = "ov13858", + .owner = THIS_MODULE, + .pm = &ov13858_pm_ops, + .acpi_match_table = ACPI_PTR(ov13858_acpi_ids), + }, + .probe = ov13858_probe, + .remove = ov13858_remove, + .id_table = ov13858_id_table, +}; + +module_i2c_driver(ov13858_i2c_driver); + +MODULE_AUTHOR("Kan, Chris <chris.kan@intel.com>"); +MODULE_AUTHOR("Rapolu, Chiranjeevi <chiranjeevi.rapolu@intel.com>"); +MODULE_AUTHOR("Yang, Hyungwoo <hyungwoo.yang@intel.com>"); +MODULE_DESCRIPTION("Omnivision ov13858 sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/ov2659.c b/drivers/media/i2c/ov2659.c index 6e6367214d40..122dd6c5eb38 100644 --- a/drivers/media/i2c/ov2659.c +++ b/drivers/media/i2c/ov2659.c @@ -42,9 +42,9 @@ #include <media/v4l2-ctrls.h> #include <media/v4l2-device.h> #include <media/v4l2-event.h> +#include <media/v4l2-fwnode.h> #include <media/v4l2-image-sizes.h> #include <media/v4l2-mediabus.h> -#include <media/v4l2-of.h> #include <media/v4l2-subdev.h> #define DRIVER_NAME "ov2659" @@ -1308,7 +1308,8 @@ static const struct v4l2_subdev_internal_ops ov2659_subdev_internal_ops = { static int ov2659_detect(struct v4l2_subdev *sd) { struct i2c_client *client = v4l2_get_subdevdata(sd); - u8 pid, ver; + u8 pid = 0; + u8 ver = 0; int ret; dev_dbg(&client->dev, "%s:\n", __func__); @@ -1346,7 +1347,7 @@ static struct ov2659_platform_data * ov2659_get_pdata(struct i2c_client *client) { struct ov2659_platform_data *pdata; - struct v4l2_of_endpoint *bus_cfg; + struct v4l2_fwnode_endpoint *bus_cfg; struct device_node *endpoint; if (!IS_ENABLED(CONFIG_OF) || !client->dev.of_node) @@ -1356,7 +1357,7 @@ ov2659_get_pdata(struct i2c_client *client) if (!endpoint) return NULL; - bus_cfg = v4l2_of_alloc_parse_endpoint(endpoint); + bus_cfg = v4l2_fwnode_endpoint_alloc_parse(of_fwnode_handle(endpoint)); if (IS_ERR(bus_cfg)) { pdata = NULL; goto done; @@ -1376,7 +1377,7 @@ ov2659_get_pdata(struct i2c_client *client) pdata->link_frequency = bus_cfg->link_frequencies[0]; done: - v4l2_of_free_endpoint(bus_cfg); + v4l2_fwnode_endpoint_free(bus_cfg); of_node_put(endpoint); return pdata; } diff --git a/drivers/media/i2c/ov5640.c b/drivers/media/i2c/ov5640.c new file mode 100644 index 000000000000..1f5b483cf334 --- /dev/null +++ b/drivers/media/i2c/ov5640.c @@ -0,0 +1,2344 @@ +/* + * Copyright (C) 2011-2013 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright (C) 2014-2017 Mentor Graphics Inc. + * + * 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; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/clkdev.h> +#include <linux/ctype.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/gpio/consumer.h> +#include <linux/regulator/consumer.h> +#include <media/v4l2-async.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-subdev.h> + +/* min/typical/max system clock (xclk) frequencies */ +#define OV5640_XCLK_MIN 6000000 +#define OV5640_XCLK_MAX 24000000 + +#define OV5640_DEFAULT_SLAVE_ID 0x3c + +#define OV5640_REG_CHIP_ID 0x300a +#define OV5640_REG_PAD_OUTPUT00 0x3019 +#define OV5640_REG_SC_PLL_CTRL0 0x3034 +#define OV5640_REG_SC_PLL_CTRL1 0x3035 +#define OV5640_REG_SC_PLL_CTRL2 0x3036 +#define OV5640_REG_SC_PLL_CTRL3 0x3037 +#define OV5640_REG_SLAVE_ID 0x3100 +#define OV5640_REG_SYS_ROOT_DIVIDER 0x3108 +#define OV5640_REG_AWB_R_GAIN 0x3400 +#define OV5640_REG_AWB_G_GAIN 0x3402 +#define OV5640_REG_AWB_B_GAIN 0x3404 +#define OV5640_REG_AWB_MANUAL_CTRL 0x3406 +#define OV5640_REG_AEC_PK_EXPOSURE_HI 0x3500 +#define OV5640_REG_AEC_PK_EXPOSURE_MED 0x3501 +#define OV5640_REG_AEC_PK_EXPOSURE_LO 0x3502 +#define OV5640_REG_AEC_PK_MANUAL 0x3503 +#define OV5640_REG_AEC_PK_REAL_GAIN 0x350a +#define OV5640_REG_AEC_PK_VTS 0x350c +#define OV5640_REG_TIMING_HTS 0x380c +#define OV5640_REG_TIMING_VTS 0x380e +#define OV5640_REG_TIMING_TC_REG21 0x3821 +#define OV5640_REG_AEC_CTRL00 0x3a00 +#define OV5640_REG_AEC_B50_STEP 0x3a08 +#define OV5640_REG_AEC_B60_STEP 0x3a0a +#define OV5640_REG_AEC_CTRL0D 0x3a0d +#define OV5640_REG_AEC_CTRL0E 0x3a0e +#define OV5640_REG_AEC_CTRL0F 0x3a0f +#define OV5640_REG_AEC_CTRL10 0x3a10 +#define OV5640_REG_AEC_CTRL11 0x3a11 +#define OV5640_REG_AEC_CTRL1B 0x3a1b +#define OV5640_REG_AEC_CTRL1E 0x3a1e +#define OV5640_REG_AEC_CTRL1F 0x3a1f +#define OV5640_REG_HZ5060_CTRL00 0x3c00 +#define OV5640_REG_HZ5060_CTRL01 0x3c01 +#define OV5640_REG_SIGMADELTA_CTRL0C 0x3c0c +#define OV5640_REG_FRAME_CTRL01 0x4202 +#define OV5640_REG_MIPI_CTRL00 0x4800 +#define OV5640_REG_DEBUG_MODE 0x4814 +#define OV5640_REG_PRE_ISP_TEST_SET1 0x503d +#define OV5640_REG_SDE_CTRL0 0x5580 +#define OV5640_REG_SDE_CTRL1 0x5581 +#define OV5640_REG_SDE_CTRL3 0x5583 +#define OV5640_REG_SDE_CTRL4 0x5584 +#define OV5640_REG_SDE_CTRL5 0x5585 +#define OV5640_REG_AVG_READOUT 0x56a1 + +enum ov5640_mode_id { + OV5640_MODE_QCIF_176_144 = 0, + OV5640_MODE_QVGA_320_240, + OV5640_MODE_VGA_640_480, + OV5640_MODE_NTSC_720_480, + OV5640_MODE_PAL_720_576, + OV5640_MODE_XGA_1024_768, + OV5640_MODE_720P_1280_720, + OV5640_MODE_1080P_1920_1080, + OV5640_MODE_QSXGA_2592_1944, + OV5640_NUM_MODES, +}; + +enum ov5640_frame_rate { + OV5640_15_FPS = 0, + OV5640_30_FPS, + OV5640_NUM_FRAMERATES, +}; + +/* + * FIXME: remove this when a subdev API becomes available + * to set the MIPI CSI-2 virtual channel. + */ +static unsigned int virtual_channel; +module_param(virtual_channel, int, 0); +MODULE_PARM_DESC(virtual_channel, + "MIPI CSI-2 virtual channel (0..3), default 0"); + +static const int ov5640_framerates[] = { + [OV5640_15_FPS] = 15, + [OV5640_30_FPS] = 30, +}; + +/* regulator supplies */ +static const char * const ov5640_supply_name[] = { + "DOVDD", /* Digital I/O (1.8V) suppply */ + "DVDD", /* Digital Core (1.5V) supply */ + "AVDD", /* Analog (2.8V) supply */ +}; + +#define OV5640_NUM_SUPPLIES ARRAY_SIZE(ov5640_supply_name) + +/* + * Image size under 1280 * 960 are SUBSAMPLING + * Image size upper 1280 * 960 are SCALING + */ +enum ov5640_downsize_mode { + SUBSAMPLING, + SCALING, +}; + +struct reg_value { + u16 reg_addr; + u8 val; + u8 mask; + u32 delay_ms; +}; + +struct ov5640_mode_info { + enum ov5640_mode_id id; + enum ov5640_downsize_mode dn_mode; + u32 width; + u32 height; + const struct reg_value *reg_data; + u32 reg_data_size; +}; + +struct ov5640_ctrls { + struct v4l2_ctrl_handler handler; + struct { + struct v4l2_ctrl *auto_exp; + struct v4l2_ctrl *exposure; + }; + struct { + struct v4l2_ctrl *auto_wb; + struct v4l2_ctrl *blue_balance; + struct v4l2_ctrl *red_balance; + }; + struct { + struct v4l2_ctrl *auto_gain; + struct v4l2_ctrl *gain; + }; + struct v4l2_ctrl *brightness; + struct v4l2_ctrl *saturation; + struct v4l2_ctrl *contrast; + struct v4l2_ctrl *hue; + struct v4l2_ctrl *test_pattern; +}; + +struct ov5640_dev { + struct i2c_client *i2c_client; + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_fwnode_endpoint ep; /* the parsed DT endpoint info */ + struct clk *xclk; /* system clock to OV5640 */ + u32 xclk_freq; + + struct regulator_bulk_data supplies[OV5640_NUM_SUPPLIES]; + struct gpio_desc *reset_gpio; + struct gpio_desc *pwdn_gpio; + + /* lock to protect all members below */ + struct mutex lock; + + int power_count; + + struct v4l2_mbus_framefmt fmt; + + const struct ov5640_mode_info *current_mode; + enum ov5640_frame_rate current_fr; + struct v4l2_fract frame_interval; + + struct ov5640_ctrls ctrls; + + u32 prev_sysclk, prev_hts; + u32 ae_low, ae_high, ae_target; + + bool pending_mode_change; + bool streaming; +}; + +static inline struct ov5640_dev *to_ov5640_dev(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ov5640_dev, sd); +} + +static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct ov5640_dev, + ctrls.handler)->sd; +} + +/* + * FIXME: all of these register tables are likely filled with + * entries that set the register to their power-on default values, + * and which are otherwise not touched by this driver. Those entries + * should be identified and removed to speed register load time + * over i2c. + */ + +static const struct reg_value ov5640_init_setting_30fps_VGA[] = { + + {0x3103, 0x11, 0, 0}, {0x3008, 0x82, 0, 5}, {0x3008, 0x42, 0, 0}, + {0x3103, 0x03, 0, 0}, {0x3017, 0x00, 0, 0}, {0x3018, 0x00, 0, 0}, + {0x3034, 0x18, 0, 0}, {0x3035, 0x14, 0, 0}, {0x3036, 0x38, 0, 0}, + {0x3037, 0x13, 0, 0}, {0x3108, 0x01, 0, 0}, {0x3630, 0x36, 0, 0}, + {0x3631, 0x0e, 0, 0}, {0x3632, 0xe2, 0, 0}, {0x3633, 0x12, 0, 0}, + {0x3621, 0xe0, 0, 0}, {0x3704, 0xa0, 0, 0}, {0x3703, 0x5a, 0, 0}, + {0x3715, 0x78, 0, 0}, {0x3717, 0x01, 0, 0}, {0x370b, 0x60, 0, 0}, + {0x3705, 0x1a, 0, 0}, {0x3905, 0x02, 0, 0}, {0x3906, 0x10, 0, 0}, + {0x3901, 0x0a, 0, 0}, {0x3731, 0x12, 0, 0}, {0x3600, 0x08, 0, 0}, + {0x3601, 0x33, 0, 0}, {0x302d, 0x60, 0, 0}, {0x3620, 0x52, 0, 0}, + {0x371b, 0x20, 0, 0}, {0x471c, 0x50, 0, 0}, {0x3a13, 0x43, 0, 0}, + {0x3a18, 0x00, 0, 0}, {0x3a19, 0xf8, 0, 0}, {0x3635, 0x13, 0, 0}, + {0x3636, 0x03, 0, 0}, {0x3634, 0x40, 0, 0}, {0x3622, 0x01, 0, 0}, + {0x3c01, 0xa4, 0, 0}, {0x3c04, 0x28, 0, 0}, {0x3c05, 0x98, 0, 0}, + {0x3c06, 0x00, 0, 0}, {0x3c07, 0x08, 0, 0}, {0x3c08, 0x00, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3820, 0x41, 0, 0}, {0x3821, 0x07, 0, 0}, {0x3814, 0x31, 0, 0}, + {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, + {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0}, + {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0}, + {0x3808, 0x02, 0, 0}, {0x3809, 0x80, 0, 0}, {0x380a, 0x01, 0, 0}, + {0x380b, 0xe0, 0, 0}, {0x380c, 0x07, 0, 0}, {0x380d, 0x68, 0, 0}, + {0x380e, 0x03, 0, 0}, {0x380f, 0xd8, 0, 0}, {0x3810, 0x00, 0, 0}, + {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x06, 0, 0}, + {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0}, + {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0}, + {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, + {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, + {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x3000, 0x00, 0, 0}, + {0x3002, 0x1c, 0, 0}, {0x3004, 0xff, 0, 0}, {0x3006, 0xc3, 0, 0}, + {0x300e, 0x45, 0, 0}, {0x302e, 0x08, 0, 0}, {0x4300, 0x3f, 0, 0}, + {0x501f, 0x00, 0, 0}, {0x4713, 0x03, 0, 0}, {0x4407, 0x04, 0, 0}, + {0x440e, 0x00, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, + {0x4837, 0x0a, 0, 0}, {0x4800, 0x04, 0, 0}, {0x3824, 0x02, 0, 0}, + {0x5000, 0xa7, 0, 0}, {0x5001, 0xa3, 0, 0}, {0x5180, 0xff, 0, 0}, + {0x5181, 0xf2, 0, 0}, {0x5182, 0x00, 0, 0}, {0x5183, 0x14, 0, 0}, + {0x5184, 0x25, 0, 0}, {0x5185, 0x24, 0, 0}, {0x5186, 0x09, 0, 0}, + {0x5187, 0x09, 0, 0}, {0x5188, 0x09, 0, 0}, {0x5189, 0x88, 0, 0}, + {0x518a, 0x54, 0, 0}, {0x518b, 0xee, 0, 0}, {0x518c, 0xb2, 0, 0}, + {0x518d, 0x50, 0, 0}, {0x518e, 0x34, 0, 0}, {0x518f, 0x6b, 0, 0}, + {0x5190, 0x46, 0, 0}, {0x5191, 0xf8, 0, 0}, {0x5192, 0x04, 0, 0}, + {0x5193, 0x70, 0, 0}, {0x5194, 0xf0, 0, 0}, {0x5195, 0xf0, 0, 0}, + {0x5196, 0x03, 0, 0}, {0x5197, 0x01, 0, 0}, {0x5198, 0x04, 0, 0}, + {0x5199, 0x6c, 0, 0}, {0x519a, 0x04, 0, 0}, {0x519b, 0x00, 0, 0}, + {0x519c, 0x09, 0, 0}, {0x519d, 0x2b, 0, 0}, {0x519e, 0x38, 0, 0}, + {0x5381, 0x1e, 0, 0}, {0x5382, 0x5b, 0, 0}, {0x5383, 0x08, 0, 0}, + {0x5384, 0x0a, 0, 0}, {0x5385, 0x7e, 0, 0}, {0x5386, 0x88, 0, 0}, + {0x5387, 0x7c, 0, 0}, {0x5388, 0x6c, 0, 0}, {0x5389, 0x10, 0, 0}, + {0x538a, 0x01, 0, 0}, {0x538b, 0x98, 0, 0}, {0x5300, 0x08, 0, 0}, + {0x5301, 0x30, 0, 0}, {0x5302, 0x10, 0, 0}, {0x5303, 0x00, 0, 0}, + {0x5304, 0x08, 0, 0}, {0x5305, 0x30, 0, 0}, {0x5306, 0x08, 0, 0}, + {0x5307, 0x16, 0, 0}, {0x5309, 0x08, 0, 0}, {0x530a, 0x30, 0, 0}, + {0x530b, 0x04, 0, 0}, {0x530c, 0x06, 0, 0}, {0x5480, 0x01, 0, 0}, + {0x5481, 0x08, 0, 0}, {0x5482, 0x14, 0, 0}, {0x5483, 0x28, 0, 0}, + {0x5484, 0x51, 0, 0}, {0x5485, 0x65, 0, 0}, {0x5486, 0x71, 0, 0}, + {0x5487, 0x7d, 0, 0}, {0x5488, 0x87, 0, 0}, {0x5489, 0x91, 0, 0}, + {0x548a, 0x9a, 0, 0}, {0x548b, 0xaa, 0, 0}, {0x548c, 0xb8, 0, 0}, + {0x548d, 0xcd, 0, 0}, {0x548e, 0xdd, 0, 0}, {0x548f, 0xea, 0, 0}, + {0x5490, 0x1d, 0, 0}, {0x5580, 0x02, 0, 0}, {0x5583, 0x40, 0, 0}, + {0x5584, 0x10, 0, 0}, {0x5589, 0x10, 0, 0}, {0x558a, 0x00, 0, 0}, + {0x558b, 0xf8, 0, 0}, {0x5800, 0x23, 0, 0}, {0x5801, 0x14, 0, 0}, + {0x5802, 0x0f, 0, 0}, {0x5803, 0x0f, 0, 0}, {0x5804, 0x12, 0, 0}, + {0x5805, 0x26, 0, 0}, {0x5806, 0x0c, 0, 0}, {0x5807, 0x08, 0, 0}, + {0x5808, 0x05, 0, 0}, {0x5809, 0x05, 0, 0}, {0x580a, 0x08, 0, 0}, + {0x580b, 0x0d, 0, 0}, {0x580c, 0x08, 0, 0}, {0x580d, 0x03, 0, 0}, + {0x580e, 0x00, 0, 0}, {0x580f, 0x00, 0, 0}, {0x5810, 0x03, 0, 0}, + {0x5811, 0x09, 0, 0}, {0x5812, 0x07, 0, 0}, {0x5813, 0x03, 0, 0}, + {0x5814, 0x00, 0, 0}, {0x5815, 0x01, 0, 0}, {0x5816, 0x03, 0, 0}, + {0x5817, 0x08, 0, 0}, {0x5818, 0x0d, 0, 0}, {0x5819, 0x08, 0, 0}, + {0x581a, 0x05, 0, 0}, {0x581b, 0x06, 0, 0}, {0x581c, 0x08, 0, 0}, + {0x581d, 0x0e, 0, 0}, {0x581e, 0x29, 0, 0}, {0x581f, 0x17, 0, 0}, + {0x5820, 0x11, 0, 0}, {0x5821, 0x11, 0, 0}, {0x5822, 0x15, 0, 0}, + {0x5823, 0x28, 0, 0}, {0x5824, 0x46, 0, 0}, {0x5825, 0x26, 0, 0}, + {0x5826, 0x08, 0, 0}, {0x5827, 0x26, 0, 0}, {0x5828, 0x64, 0, 0}, + {0x5829, 0x26, 0, 0}, {0x582a, 0x24, 0, 0}, {0x582b, 0x22, 0, 0}, + {0x582c, 0x24, 0, 0}, {0x582d, 0x24, 0, 0}, {0x582e, 0x06, 0, 0}, + {0x582f, 0x22, 0, 0}, {0x5830, 0x40, 0, 0}, {0x5831, 0x42, 0, 0}, + {0x5832, 0x24, 0, 0}, {0x5833, 0x26, 0, 0}, {0x5834, 0x24, 0, 0}, + {0x5835, 0x22, 0, 0}, {0x5836, 0x22, 0, 0}, {0x5837, 0x26, 0, 0}, + {0x5838, 0x44, 0, 0}, {0x5839, 0x24, 0, 0}, {0x583a, 0x26, 0, 0}, + {0x583b, 0x28, 0, 0}, {0x583c, 0x42, 0, 0}, {0x583d, 0xce, 0, 0}, + {0x5025, 0x00, 0, 0}, {0x3a0f, 0x30, 0, 0}, {0x3a10, 0x28, 0, 0}, + {0x3a1b, 0x30, 0, 0}, {0x3a1e, 0x26, 0, 0}, {0x3a11, 0x60, 0, 0}, + {0x3a1f, 0x14, 0, 0}, {0x3008, 0x02, 0, 0}, {0x3c00, 0x04, 0, 300}, +}; + +static const struct reg_value ov5640_setting_30fps_VGA_640_480[] = { + + {0x3035, 0x14, 0, 0}, {0x3036, 0x38, 0, 0}, {0x3c07, 0x08, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3820, 0x41, 0, 0}, {0x3821, 0x07, 0, 0}, {0x3814, 0x31, 0, 0}, + {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, + {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0}, + {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0}, + {0x3808, 0x02, 0, 0}, {0x3809, 0x80, 0, 0}, {0x380a, 0x01, 0, 0}, + {0x380b, 0xe0, 0, 0}, {0x380c, 0x07, 0, 0}, {0x380d, 0x68, 0, 0}, + {0x380e, 0x04, 0, 0}, {0x380f, 0x38, 0, 0}, {0x3810, 0x00, 0, 0}, + {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x06, 0, 0}, + {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0}, + {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0}, + {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x0e, 0, 0}, + {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, + {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x4713, 0x03, 0, 0}, + {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, + {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0}, {0x3503, 0x00, 0, 0}, +}; + +static const struct reg_value ov5640_setting_15fps_VGA_640_480[] = { + {0x3035, 0x22, 0, 0}, {0x3036, 0x38, 0, 0}, {0x3c07, 0x08, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3820, 0x41, 0, 0}, {0x3821, 0x07, 0, 0}, {0x3814, 0x31, 0, 0}, + {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, + {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0}, + {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0}, + {0x3808, 0x02, 0, 0}, {0x3809, 0x80, 0, 0}, {0x380a, 0x01, 0, 0}, + {0x380b, 0xe0, 0, 0}, {0x380c, 0x07, 0, 0}, {0x380d, 0x68, 0, 0}, + {0x380e, 0x03, 0, 0}, {0x380f, 0xd8, 0, 0}, {0x3810, 0x00, 0, 0}, + {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x06, 0, 0}, + {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0}, + {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0}, + {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, + {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, + {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x4713, 0x03, 0, 0}, + {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, + {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0}, +}; + +static const struct reg_value ov5640_setting_30fps_XGA_1024_768[] = { + + {0x3035, 0x14, 0, 0}, {0x3036, 0x38, 0, 0}, {0x3c07, 0x08, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3820, 0x41, 0, 0}, {0x3821, 0x07, 0, 0}, {0x3814, 0x31, 0, 0}, + {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, + {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0}, + {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0}, + {0x3808, 0x02, 0, 0}, {0x3809, 0x80, 0, 0}, {0x380a, 0x01, 0, 0}, + {0x380b, 0xe0, 0, 0}, {0x380c, 0x07, 0, 0}, {0x380d, 0x68, 0, 0}, + {0x380e, 0x04, 0, 0}, {0x380f, 0x38, 0, 0}, {0x3810, 0x00, 0, 0}, + {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x06, 0, 0}, + {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0}, + {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0}, + {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x0e, 0, 0}, + {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, + {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x4713, 0x03, 0, 0}, + {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, + {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0}, {0x3503, 0x00, 0, 0}, + {0x3808, 0x04, 0, 0}, {0x3809, 0x00, 0, 0}, {0x380a, 0x03, 0, 0}, + {0x380b, 0x00, 0, 0}, {0x3035, 0x12, 0, 0}, +}; + +static const struct reg_value ov5640_setting_15fps_XGA_1024_768[] = { + {0x3035, 0x22, 0, 0}, {0x3036, 0x38, 0, 0}, {0x3c07, 0x08, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3820, 0x41, 0, 0}, {0x3821, 0x07, 0, 0}, {0x3814, 0x31, 0, 0}, + {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, + {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0}, + {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0}, + {0x3808, 0x02, 0, 0}, {0x3809, 0x80, 0, 0}, {0x380a, 0x01, 0, 0}, + {0x380b, 0xe0, 0, 0}, {0x380c, 0x07, 0, 0}, {0x380d, 0x68, 0, 0}, + {0x380e, 0x03, 0, 0}, {0x380f, 0xd8, 0, 0}, {0x3810, 0x00, 0, 0}, + {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x06, 0, 0}, + {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0}, + {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0}, + {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, + {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, + {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x4713, 0x03, 0, 0}, + {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, + {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0}, {0x3808, 0x04, 0, 0}, + {0x3809, 0x00, 0, 0}, {0x380a, 0x03, 0, 0}, {0x380b, 0x00, 0, 0}, +}; + +static const struct reg_value ov5640_setting_30fps_QVGA_320_240[] = { + {0x3035, 0x14, 0, 0}, {0x3036, 0x38, 0, 0}, {0x3c07, 0x08, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3820, 0x41, 0, 0}, {0x3821, 0x07, 0, 0}, {0x3814, 0x31, 0, 0}, + {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, + {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0}, + {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0}, + {0x3808, 0x01, 0, 0}, {0x3809, 0x40, 0, 0}, {0x380a, 0x00, 0, 0}, + {0x380b, 0xf0, 0, 0}, {0x380c, 0x07, 0, 0}, {0x380d, 0x68, 0, 0}, + {0x380e, 0x03, 0, 0}, {0x380f, 0xd8, 0, 0}, {0x3810, 0x00, 0, 0}, + {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x06, 0, 0}, + {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0}, + {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0}, + {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, + {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, + {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x4713, 0x03, 0, 0}, + {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, + {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0}, +}; + +static const struct reg_value ov5640_setting_15fps_QVGA_320_240[] = { + {0x3035, 0x22, 0, 0}, {0x3036, 0x38, 0, 0}, {0x3c07, 0x08, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3820, 0x41, 0, 0}, {0x3821, 0x07, 0, 0}, {0x3814, 0x31, 0, 0}, + {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, + {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0}, + {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0}, + {0x3808, 0x01, 0, 0}, {0x3809, 0x40, 0, 0}, {0x380a, 0x00, 0, 0}, + {0x380b, 0xf0, 0, 0}, {0x380c, 0x07, 0, 0}, {0x380d, 0x68, 0, 0}, + {0x380e, 0x03, 0, 0}, {0x380f, 0xd8, 0, 0}, {0x3810, 0x00, 0, 0}, + {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x06, 0, 0}, + {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0}, + {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0}, + {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, + {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, + {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x4713, 0x03, 0, 0}, + {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, + {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0}, +}; + +static const struct reg_value ov5640_setting_30fps_QCIF_176_144[] = { + {0x3035, 0x14, 0, 0}, {0x3036, 0x38, 0, 0}, {0x3c07, 0x08, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3820, 0x41, 0, 0}, {0x3821, 0x07, 0, 0}, {0x3814, 0x31, 0, 0}, + {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, + {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0}, + {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0}, + {0x3808, 0x00, 0, 0}, {0x3809, 0xb0, 0, 0}, {0x380a, 0x00, 0, 0}, + {0x380b, 0x90, 0, 0}, {0x380c, 0x07, 0, 0}, {0x380d, 0x68, 0, 0}, + {0x380e, 0x03, 0, 0}, {0x380f, 0xd8, 0, 0}, {0x3810, 0x00, 0, 0}, + {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x06, 0, 0}, + {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0}, + {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0}, + {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, + {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, + {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x4713, 0x03, 0, 0}, + {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, + {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0}, +}; +static const struct reg_value ov5640_setting_15fps_QCIF_176_144[] = { + {0x3035, 0x22, 0, 0}, {0x3036, 0x38, 0, 0}, {0x3c07, 0x08, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3820, 0x41, 0, 0}, {0x3821, 0x07, 0, 0}, {0x3814, 0x31, 0, 0}, + {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, + {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0}, + {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0}, + {0x3808, 0x00, 0, 0}, {0x3809, 0xb0, 0, 0}, {0x380a, 0x00, 0, 0}, + {0x380b, 0x90, 0, 0}, {0x380c, 0x07, 0, 0}, {0x380d, 0x68, 0, 0}, + {0x380e, 0x03, 0, 0}, {0x380f, 0xd8, 0, 0}, {0x3810, 0x00, 0, 0}, + {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x06, 0, 0}, + {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0}, + {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0}, + {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, + {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, + {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x4713, 0x03, 0, 0}, + {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, + {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0}, +}; + +static const struct reg_value ov5640_setting_30fps_NTSC_720_480[] = { + {0x3035, 0x12, 0, 0}, {0x3036, 0x38, 0, 0}, {0x3c07, 0x08, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3820, 0x41, 0, 0}, {0x3821, 0x07, 0, 0}, {0x3814, 0x31, 0, 0}, + {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, + {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0}, + {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0}, + {0x3808, 0x02, 0, 0}, {0x3809, 0xd0, 0, 0}, {0x380a, 0x01, 0, 0}, + {0x380b, 0xe0, 0, 0}, {0x380c, 0x07, 0, 0}, {0x380d, 0x68, 0, 0}, + {0x380e, 0x03, 0, 0}, {0x380f, 0xd8, 0, 0}, {0x3810, 0x00, 0, 0}, + {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x3c, 0, 0}, + {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0}, + {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0}, + {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, + {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, + {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x4713, 0x03, 0, 0}, + {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, + {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0}, +}; + +static const struct reg_value ov5640_setting_15fps_NTSC_720_480[] = { + {0x3035, 0x22, 0, 0}, {0x3036, 0x38, 0, 0}, {0x3c07, 0x08, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3820, 0x41, 0, 0}, {0x3821, 0x07, 0, 0}, {0x3814, 0x31, 0, 0}, + {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, + {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0}, + {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0}, + {0x3808, 0x02, 0, 0}, {0x3809, 0xd0, 0, 0}, {0x380a, 0x01, 0, 0}, + {0x380b, 0xe0, 0, 0}, {0x380c, 0x07, 0, 0}, {0x380d, 0x68, 0, 0}, + {0x380e, 0x03, 0, 0}, {0x380f, 0xd8, 0, 0}, {0x3810, 0x00, 0, 0}, + {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x3c, 0, 0}, + {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0}, + {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0}, + {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, + {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, + {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x4713, 0x03, 0, 0}, + {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, + {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0}, +}; + +static const struct reg_value ov5640_setting_30fps_PAL_720_576[] = { + {0x3035, 0x12, 0, 0}, {0x3036, 0x38, 0, 0}, {0x3c07, 0x08, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3820, 0x41, 0, 0}, {0x3821, 0x07, 0, 0}, {0x3814, 0x31, 0, 0}, + {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, + {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0}, + {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0}, + {0x3808, 0x02, 0, 0}, {0x3809, 0xd0, 0, 0}, {0x380a, 0x02, 0, 0}, + {0x380b, 0x40, 0, 0}, {0x380c, 0x07, 0, 0}, {0x380d, 0x68, 0, 0}, + {0x380e, 0x03, 0, 0}, {0x380f, 0xd8, 0, 0}, {0x3810, 0x00, 0, 0}, + {0x3811, 0x38, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x06, 0, 0}, + {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0}, + {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0}, + {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, + {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, + {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x4713, 0x03, 0, 0}, + {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, + {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0}, +}; + +static const struct reg_value ov5640_setting_15fps_PAL_720_576[] = { + {0x3035, 0x22, 0, 0}, {0x3036, 0x38, 0, 0}, {0x3c07, 0x08, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3820, 0x41, 0, 0}, {0x3821, 0x07, 0, 0}, {0x3814, 0x31, 0, 0}, + {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, + {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0}, + {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0}, + {0x3808, 0x02, 0, 0}, {0x3809, 0xd0, 0, 0}, {0x380a, 0x02, 0, 0}, + {0x380b, 0x40, 0, 0}, {0x380c, 0x07, 0, 0}, {0x380d, 0x68, 0, 0}, + {0x380e, 0x03, 0, 0}, {0x380f, 0xd8, 0, 0}, {0x3810, 0x00, 0, 0}, + {0x3811, 0x38, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x06, 0, 0}, + {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0}, + {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0}, + {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, + {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, + {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x4713, 0x03, 0, 0}, + {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, + {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0}, +}; + +static const struct reg_value ov5640_setting_30fps_720P_1280_720[] = { + {0x3008, 0x42, 0, 0}, + {0x3035, 0x21, 0, 0}, {0x3036, 0x54, 0, 0}, {0x3c07, 0x07, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3820, 0x41, 0, 0}, {0x3821, 0x07, 0, 0}, {0x3814, 0x31, 0, 0}, + {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, + {0x3802, 0x00, 0, 0}, {0x3803, 0xfa, 0, 0}, {0x3804, 0x0a, 0, 0}, + {0x3805, 0x3f, 0, 0}, {0x3806, 0x06, 0, 0}, {0x3807, 0xa9, 0, 0}, + {0x3808, 0x05, 0, 0}, {0x3809, 0x00, 0, 0}, {0x380a, 0x02, 0, 0}, + {0x380b, 0xd0, 0, 0}, {0x380c, 0x07, 0, 0}, {0x380d, 0x64, 0, 0}, + {0x380e, 0x02, 0, 0}, {0x380f, 0xe4, 0, 0}, {0x3810, 0x00, 0, 0}, + {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x04, 0, 0}, + {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0}, + {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x02, 0, 0}, + {0x3a03, 0xe4, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0xbc, 0, 0}, + {0x3a0a, 0x01, 0, 0}, {0x3a0b, 0x72, 0, 0}, {0x3a0e, 0x01, 0, 0}, + {0x3a0d, 0x02, 0, 0}, {0x3a14, 0x02, 0, 0}, {0x3a15, 0xe4, 0, 0}, + {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x4713, 0x02, 0, 0}, + {0x4407, 0x04, 0, 0}, {0x460b, 0x37, 0, 0}, {0x460c, 0x20, 0, 0}, + {0x3824, 0x04, 0, 0}, {0x5001, 0x83, 0, 0}, {0x4005, 0x1a, 0, 0}, + {0x3008, 0x02, 0, 0}, {0x3503, 0, 0, 0}, +}; + +static const struct reg_value ov5640_setting_15fps_720P_1280_720[] = { + {0x3035, 0x41, 0, 0}, {0x3036, 0x54, 0, 0}, {0x3c07, 0x07, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3820, 0x41, 0, 0}, {0x3821, 0x07, 0, 0}, {0x3814, 0x31, 0, 0}, + {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, + {0x3802, 0x00, 0, 0}, {0x3803, 0xfa, 0, 0}, {0x3804, 0x0a, 0, 0}, + {0x3805, 0x3f, 0, 0}, {0x3806, 0x06, 0, 0}, {0x3807, 0xa9, 0, 0}, + {0x3808, 0x05, 0, 0}, {0x3809, 0x00, 0, 0}, {0x380a, 0x02, 0, 0}, + {0x380b, 0xd0, 0, 0}, {0x380c, 0x07, 0, 0}, {0x380d, 0x64, 0, 0}, + {0x380e, 0x02, 0, 0}, {0x380f, 0xe4, 0, 0}, {0x3810, 0x00, 0, 0}, + {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x04, 0, 0}, + {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0}, + {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x02, 0, 0}, + {0x3a03, 0xe4, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0xbc, 0, 0}, + {0x3a0a, 0x01, 0, 0}, {0x3a0b, 0x72, 0, 0}, {0x3a0e, 0x01, 0, 0}, + {0x3a0d, 0x02, 0, 0}, {0x3a14, 0x02, 0, 0}, {0x3a15, 0xe4, 0, 0}, + {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x4713, 0x02, 0, 0}, + {0x4407, 0x04, 0, 0}, {0x460b, 0x37, 0, 0}, {0x460c, 0x20, 0, 0}, + {0x3824, 0x04, 0, 0}, {0x5001, 0x83, 0, 0}, +}; + +static const struct reg_value ov5640_setting_30fps_1080P_1920_1080[] = { + {0x3008, 0x42, 0, 0}, + {0x3035, 0x21, 0, 0}, {0x3036, 0x54, 0, 0}, {0x3c07, 0x08, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3820, 0x40, 0, 0}, {0x3821, 0x06, 0, 0}, {0x3814, 0x11, 0, 0}, + {0x3815, 0x11, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, + {0x3802, 0x00, 0, 0}, {0x3803, 0x00, 0, 0}, {0x3804, 0x0a, 0, 0}, + {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9f, 0, 0}, + {0x3808, 0x0a, 0, 0}, {0x3809, 0x20, 0, 0}, {0x380a, 0x07, 0, 0}, + {0x380b, 0x98, 0, 0}, {0x380c, 0x0b, 0, 0}, {0x380d, 0x1c, 0, 0}, + {0x380e, 0x07, 0, 0}, {0x380f, 0xb0, 0, 0}, {0x3810, 0x00, 0, 0}, + {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x04, 0, 0}, + {0x3618, 0x04, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x21, 0, 0}, + {0x3709, 0x12, 0, 0}, {0x370c, 0x00, 0, 0}, {0x3a02, 0x03, 0, 0}, + {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, + {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, + {0x4001, 0x02, 0, 0}, {0x4004, 0x06, 0, 0}, {0x4713, 0x03, 0, 0}, + {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, + {0x3824, 0x02, 0, 0}, {0x5001, 0x83, 0, 0}, {0x3035, 0x11, 0, 0}, + {0x3036, 0x54, 0, 0}, {0x3c07, 0x07, 0, 0}, {0x3c08, 0x00, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3800, 0x01, 0, 0}, {0x3801, 0x50, 0, 0}, {0x3802, 0x01, 0, 0}, + {0x3803, 0xb2, 0, 0}, {0x3804, 0x08, 0, 0}, {0x3805, 0xef, 0, 0}, + {0x3806, 0x05, 0, 0}, {0x3807, 0xf1, 0, 0}, {0x3808, 0x07, 0, 0}, + {0x3809, 0x80, 0, 0}, {0x380a, 0x04, 0, 0}, {0x380b, 0x38, 0, 0}, + {0x380c, 0x09, 0, 0}, {0x380d, 0xc4, 0, 0}, {0x380e, 0x04, 0, 0}, + {0x380f, 0x60, 0, 0}, {0x3612, 0x2b, 0, 0}, {0x3708, 0x64, 0, 0}, + {0x3a02, 0x04, 0, 0}, {0x3a03, 0x60, 0, 0}, {0x3a08, 0x01, 0, 0}, + {0x3a09, 0x50, 0, 0}, {0x3a0a, 0x01, 0, 0}, {0x3a0b, 0x18, 0, 0}, + {0x3a0e, 0x03, 0, 0}, {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x04, 0, 0}, + {0x3a15, 0x60, 0, 0}, {0x4713, 0x02, 0, 0}, {0x4407, 0x04, 0, 0}, + {0x460b, 0x37, 0, 0}, {0x460c, 0x20, 0, 0}, {0x3824, 0x04, 0, 0}, + {0x4005, 0x1a, 0, 0}, {0x3008, 0x02, 0, 0}, + {0x3503, 0, 0, 0}, +}; + +static const struct reg_value ov5640_setting_15fps_1080P_1920_1080[] = { + {0x3008, 0x42, 0, 0}, + {0x3035, 0x21, 0, 0}, {0x3036, 0x54, 0, 0}, {0x3c07, 0x08, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3820, 0x40, 0, 0}, {0x3821, 0x06, 0, 0}, {0x3814, 0x11, 0, 0}, + {0x3815, 0x11, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, + {0x3802, 0x00, 0, 0}, {0x3803, 0x00, 0, 0}, {0x3804, 0x0a, 0, 0}, + {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9f, 0, 0}, + {0x3808, 0x0a, 0, 0}, {0x3809, 0x20, 0, 0}, {0x380a, 0x07, 0, 0}, + {0x380b, 0x98, 0, 0}, {0x380c, 0x0b, 0, 0}, {0x380d, 0x1c, 0, 0}, + {0x380e, 0x07, 0, 0}, {0x380f, 0xb0, 0, 0}, {0x3810, 0x00, 0, 0}, + {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x04, 0, 0}, + {0x3618, 0x04, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x21, 0, 0}, + {0x3709, 0x12, 0, 0}, {0x370c, 0x00, 0, 0}, {0x3a02, 0x03, 0, 0}, + {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, + {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, + {0x4001, 0x02, 0, 0}, {0x4004, 0x06, 0, 0}, {0x4713, 0x03, 0, 0}, + {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, + {0x3824, 0x02, 0, 0}, {0x5001, 0x83, 0, 0}, {0x3035, 0x21, 0, 0}, + {0x3036, 0x54, 0, 1}, {0x3c07, 0x07, 0, 0}, {0x3c08, 0x00, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3800, 0x01, 0, 0}, {0x3801, 0x50, 0, 0}, {0x3802, 0x01, 0, 0}, + {0x3803, 0xb2, 0, 0}, {0x3804, 0x08, 0, 0}, {0x3805, 0xef, 0, 0}, + {0x3806, 0x05, 0, 0}, {0x3807, 0xf1, 0, 0}, {0x3808, 0x07, 0, 0}, + {0x3809, 0x80, 0, 0}, {0x380a, 0x04, 0, 0}, {0x380b, 0x38, 0, 0}, + {0x380c, 0x09, 0, 0}, {0x380d, 0xc4, 0, 0}, {0x380e, 0x04, 0, 0}, + {0x380f, 0x60, 0, 0}, {0x3612, 0x2b, 0, 0}, {0x3708, 0x64, 0, 0}, + {0x3a02, 0x04, 0, 0}, {0x3a03, 0x60, 0, 0}, {0x3a08, 0x01, 0, 0}, + {0x3a09, 0x50, 0, 0}, {0x3a0a, 0x01, 0, 0}, {0x3a0b, 0x18, 0, 0}, + {0x3a0e, 0x03, 0, 0}, {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x04, 0, 0}, + {0x3a15, 0x60, 0, 0}, {0x4713, 0x02, 0, 0}, {0x4407, 0x04, 0, 0}, + {0x460b, 0x37, 0, 0}, {0x460c, 0x20, 0, 0}, {0x3824, 0x04, 0, 0}, + {0x4005, 0x1a, 0, 0}, {0x3008, 0x02, 0, 0}, {0x3503, 0, 0, 0}, +}; + +static const struct reg_value ov5640_setting_15fps_QSXGA_2592_1944[] = { + {0x3820, 0x40, 0, 0}, {0x3821, 0x06, 0, 0}, + {0x3035, 0x21, 0, 0}, {0x3036, 0x54, 0, 0}, {0x3c07, 0x08, 0, 0}, + {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0}, + {0x3820, 0x40, 0, 0}, {0x3821, 0x06, 0, 0}, {0x3814, 0x11, 0, 0}, + {0x3815, 0x11, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0}, + {0x3802, 0x00, 0, 0}, {0x3803, 0x00, 0, 0}, {0x3804, 0x0a, 0, 0}, + {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9f, 0, 0}, + {0x3808, 0x0a, 0, 0}, {0x3809, 0x20, 0, 0}, {0x380a, 0x07, 0, 0}, + {0x380b, 0x98, 0, 0}, {0x380c, 0x0b, 0, 0}, {0x380d, 0x1c, 0, 0}, + {0x380e, 0x07, 0, 0}, {0x380f, 0xb0, 0, 0}, {0x3810, 0x00, 0, 0}, + {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x04, 0, 0}, + {0x3618, 0x04, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x21, 0, 0}, + {0x3709, 0x12, 0, 0}, {0x370c, 0x00, 0, 0}, {0x3a02, 0x03, 0, 0}, + {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0}, + {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0}, + {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0}, + {0x4001, 0x02, 0, 0}, {0x4004, 0x06, 0, 0}, {0x4713, 0x03, 0, 0}, + {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0}, + {0x3824, 0x02, 0, 0}, {0x5001, 0x83, 0, 70}, +}; + +/* power-on sensor init reg table */ +static const struct ov5640_mode_info ov5640_mode_init_data = { + 0, SUBSAMPLING, 640, 480, ov5640_init_setting_30fps_VGA, + ARRAY_SIZE(ov5640_init_setting_30fps_VGA), +}; + +static const struct ov5640_mode_info +ov5640_mode_data[OV5640_NUM_FRAMERATES][OV5640_NUM_MODES] = { + { + {OV5640_MODE_QCIF_176_144, SUBSAMPLING, 176, 144, + ov5640_setting_15fps_QCIF_176_144, + ARRAY_SIZE(ov5640_setting_15fps_QCIF_176_144)}, + {OV5640_MODE_QVGA_320_240, SUBSAMPLING, 320, 240, + ov5640_setting_15fps_QVGA_320_240, + ARRAY_SIZE(ov5640_setting_15fps_QVGA_320_240)}, + {OV5640_MODE_VGA_640_480, SUBSAMPLING, 640, 480, + ov5640_setting_15fps_VGA_640_480, + ARRAY_SIZE(ov5640_setting_15fps_VGA_640_480)}, + {OV5640_MODE_NTSC_720_480, SUBSAMPLING, 720, 480, + ov5640_setting_15fps_NTSC_720_480, + ARRAY_SIZE(ov5640_setting_15fps_NTSC_720_480)}, + {OV5640_MODE_PAL_720_576, SUBSAMPLING, 720, 576, + ov5640_setting_15fps_PAL_720_576, + ARRAY_SIZE(ov5640_setting_15fps_PAL_720_576)}, + {OV5640_MODE_XGA_1024_768, SUBSAMPLING, 1024, 768, + ov5640_setting_15fps_XGA_1024_768, + ARRAY_SIZE(ov5640_setting_15fps_XGA_1024_768)}, + {OV5640_MODE_720P_1280_720, SUBSAMPLING, 1280, 720, + ov5640_setting_15fps_720P_1280_720, + ARRAY_SIZE(ov5640_setting_15fps_720P_1280_720)}, + {OV5640_MODE_1080P_1920_1080, SCALING, 1920, 1080, + ov5640_setting_15fps_1080P_1920_1080, + ARRAY_SIZE(ov5640_setting_15fps_1080P_1920_1080)}, + {OV5640_MODE_QSXGA_2592_1944, SCALING, 2592, 1944, + ov5640_setting_15fps_QSXGA_2592_1944, + ARRAY_SIZE(ov5640_setting_15fps_QSXGA_2592_1944)}, + }, { + {OV5640_MODE_QCIF_176_144, SUBSAMPLING, 176, 144, + ov5640_setting_30fps_QCIF_176_144, + ARRAY_SIZE(ov5640_setting_30fps_QCIF_176_144)}, + {OV5640_MODE_QVGA_320_240, SUBSAMPLING, 320, 240, + ov5640_setting_30fps_QVGA_320_240, + ARRAY_SIZE(ov5640_setting_30fps_QVGA_320_240)}, + {OV5640_MODE_VGA_640_480, SUBSAMPLING, 640, 480, + ov5640_setting_30fps_VGA_640_480, + ARRAY_SIZE(ov5640_setting_30fps_VGA_640_480)}, + {OV5640_MODE_NTSC_720_480, SUBSAMPLING, 720, 480, + ov5640_setting_30fps_NTSC_720_480, + ARRAY_SIZE(ov5640_setting_30fps_NTSC_720_480)}, + {OV5640_MODE_PAL_720_576, SUBSAMPLING, 720, 576, + ov5640_setting_30fps_PAL_720_576, + ARRAY_SIZE(ov5640_setting_30fps_PAL_720_576)}, + {OV5640_MODE_XGA_1024_768, SUBSAMPLING, 1024, 768, + ov5640_setting_30fps_XGA_1024_768, + ARRAY_SIZE(ov5640_setting_30fps_XGA_1024_768)}, + {OV5640_MODE_720P_1280_720, SUBSAMPLING, 1280, 720, + ov5640_setting_30fps_720P_1280_720, + ARRAY_SIZE(ov5640_setting_30fps_720P_1280_720)}, + {OV5640_MODE_1080P_1920_1080, SCALING, 1920, 1080, + ov5640_setting_30fps_1080P_1920_1080, + ARRAY_SIZE(ov5640_setting_30fps_1080P_1920_1080)}, + {OV5640_MODE_QSXGA_2592_1944, -1, 0, 0, NULL, 0}, + }, +}; + +static int ov5640_init_slave_id(struct ov5640_dev *sensor) +{ + struct i2c_client *client = sensor->i2c_client; + struct i2c_msg msg; + u8 buf[3]; + int ret; + + if (client->addr == OV5640_DEFAULT_SLAVE_ID) + return 0; + + buf[0] = OV5640_REG_SLAVE_ID >> 8; + buf[1] = OV5640_REG_SLAVE_ID & 0xff; + buf[2] = client->addr << 1; + + msg.addr = OV5640_DEFAULT_SLAVE_ID; + msg.flags = 0; + msg.buf = buf; + msg.len = sizeof(buf); + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret < 0) { + dev_err(&client->dev, "%s: failed with %d\n", __func__, ret); + return ret; + } + + return 0; +} + +static int ov5640_write_reg(struct ov5640_dev *sensor, u16 reg, u8 val) +{ + struct i2c_client *client = sensor->i2c_client; + struct i2c_msg msg; + u8 buf[3]; + int ret; + + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + buf[2] = val; + + msg.addr = client->addr; + msg.flags = client->flags; + msg.buf = buf; + msg.len = sizeof(buf); + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret < 0) { + v4l2_err(&sensor->sd, "%s: error: reg=%x, val=%x\n", + __func__, reg, val); + return ret; + } + + return 0; +} + +static int ov5640_read_reg(struct ov5640_dev *sensor, u16 reg, u8 *val) +{ + struct i2c_client *client = sensor->i2c_client; + struct i2c_msg msg[2]; + u8 buf[2]; + int ret; + + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + + msg[0].addr = client->addr; + msg[0].flags = client->flags; + msg[0].buf = buf; + msg[0].len = sizeof(buf); + + msg[1].addr = client->addr; + msg[1].flags = client->flags | I2C_M_RD; + msg[1].buf = buf; + msg[1].len = 1; + + ret = i2c_transfer(client->adapter, msg, 2); + if (ret < 0) + return ret; + + *val = buf[0]; + return 0; +} + +static int ov5640_read_reg16(struct ov5640_dev *sensor, u16 reg, u16 *val) +{ + u8 hi, lo; + int ret; + + ret = ov5640_read_reg(sensor, reg, &hi); + if (ret) + return ret; + ret = ov5640_read_reg(sensor, reg+1, &lo); + if (ret) + return ret; + + *val = ((u16)hi << 8) | (u16)lo; + return 0; +} + +static int ov5640_write_reg16(struct ov5640_dev *sensor, u16 reg, u16 val) +{ + int ret; + + ret = ov5640_write_reg(sensor, reg, val >> 8); + if (ret) + return ret; + + return ov5640_write_reg(sensor, reg + 1, val & 0xff); +} + +static int ov5640_mod_reg(struct ov5640_dev *sensor, u16 reg, + u8 mask, u8 val) +{ + u8 readval; + int ret; + + ret = ov5640_read_reg(sensor, reg, &readval); + if (ret) + return ret; + + readval &= ~mask; + val &= mask; + val |= readval; + + return ov5640_write_reg(sensor, reg, val); +} + +/* download ov5640 settings to sensor through i2c */ +static int ov5640_load_regs(struct ov5640_dev *sensor, + const struct ov5640_mode_info *mode) +{ + const struct reg_value *regs = mode->reg_data; + unsigned int i; + u32 delay_ms; + u16 reg_addr; + u8 mask, val; + int ret = 0; + + for (i = 0; i < mode->reg_data_size; ++i, ++regs) { + delay_ms = regs->delay_ms; + reg_addr = regs->reg_addr; + val = regs->val; + mask = regs->mask; + + if (mask) + ret = ov5640_mod_reg(sensor, reg_addr, mask, val); + else + ret = ov5640_write_reg(sensor, reg_addr, val); + if (ret) + break; + + if (delay_ms) + usleep_range(1000*delay_ms, 1000*delay_ms+100); + } + + return ret; +} + +/* read exposure, in number of line periods */ +static int ov5640_get_exposure(struct ov5640_dev *sensor) +{ + int exp, ret; + u8 temp; + + ret = ov5640_read_reg(sensor, OV5640_REG_AEC_PK_EXPOSURE_HI, &temp); + if (ret) + return ret; + exp = ((int)temp & 0x0f) << 16; + ret = ov5640_read_reg(sensor, OV5640_REG_AEC_PK_EXPOSURE_MED, &temp); + if (ret) + return ret; + exp |= ((int)temp << 8); + ret = ov5640_read_reg(sensor, OV5640_REG_AEC_PK_EXPOSURE_LO, &temp); + if (ret) + return ret; + exp |= (int)temp; + + return exp >> 4; +} + +/* write exposure, given number of line periods */ +static int ov5640_set_exposure(struct ov5640_dev *sensor, u32 exposure) +{ + int ret; + + exposure <<= 4; + + ret = ov5640_write_reg(sensor, + OV5640_REG_AEC_PK_EXPOSURE_LO, + exposure & 0xff); + if (ret) + return ret; + ret = ov5640_write_reg(sensor, + OV5640_REG_AEC_PK_EXPOSURE_MED, + (exposure >> 8) & 0xff); + if (ret) + return ret; + return ov5640_write_reg(sensor, + OV5640_REG_AEC_PK_EXPOSURE_HI, + (exposure >> 16) & 0x0f); +} + +static int ov5640_get_gain(struct ov5640_dev *sensor) +{ + u16 gain; + int ret; + + ret = ov5640_read_reg16(sensor, OV5640_REG_AEC_PK_REAL_GAIN, &gain); + if (ret) + return ret; + + return gain & 0x3ff; +} + +static int ov5640_set_stream(struct ov5640_dev *sensor, bool on) +{ + int ret; + + ret = ov5640_mod_reg(sensor, OV5640_REG_MIPI_CTRL00, BIT(5), + on ? 0 : BIT(5)); + if (ret) + return ret; + ret = ov5640_write_reg(sensor, OV5640_REG_PAD_OUTPUT00, + on ? 0x00 : 0x70); + if (ret) + return ret; + + return ov5640_write_reg(sensor, OV5640_REG_FRAME_CTRL01, + on ? 0x00 : 0x0f); +} + +static int ov5640_get_sysclk(struct ov5640_dev *sensor) +{ + /* calculate sysclk */ + u32 xvclk = sensor->xclk_freq / 10000; + u32 multiplier, prediv, VCO, sysdiv, pll_rdiv; + u32 sclk_rdiv_map[] = {1, 2, 4, 8}; + u32 bit_div2x = 1, sclk_rdiv, sysclk; + u8 temp1, temp2; + int ret; + + ret = ov5640_read_reg(sensor, OV5640_REG_SC_PLL_CTRL0, &temp1); + if (ret) + return ret; + temp2 = temp1 & 0x0f; + if (temp2 == 8 || temp2 == 10) + bit_div2x = temp2 / 2; + + ret = ov5640_read_reg(sensor, OV5640_REG_SC_PLL_CTRL1, &temp1); + if (ret) + return ret; + sysdiv = temp1 >> 4; + if (sysdiv == 0) + sysdiv = 16; + + ret = ov5640_read_reg(sensor, OV5640_REG_SC_PLL_CTRL2, &temp1); + if (ret) + return ret; + multiplier = temp1; + + ret = ov5640_read_reg(sensor, OV5640_REG_SC_PLL_CTRL3, &temp1); + if (ret) + return ret; + prediv = temp1 & 0x0f; + pll_rdiv = ((temp1 >> 4) & 0x01) + 1; + + ret = ov5640_read_reg(sensor, OV5640_REG_SYS_ROOT_DIVIDER, &temp1); + if (ret) + return ret; + temp2 = temp1 & 0x03; + sclk_rdiv = sclk_rdiv_map[temp2]; + + if (!prediv || !sysdiv || !pll_rdiv || !bit_div2x) + return -EINVAL; + + VCO = xvclk * multiplier / prediv; + + sysclk = VCO / sysdiv / pll_rdiv * 2 / bit_div2x / sclk_rdiv; + + return sysclk; +} + +static int ov5640_set_night_mode(struct ov5640_dev *sensor) +{ + /* read HTS from register settings */ + u8 mode; + int ret; + + ret = ov5640_read_reg(sensor, OV5640_REG_AEC_CTRL00, &mode); + if (ret) + return ret; + mode &= 0xfb; + return ov5640_write_reg(sensor, OV5640_REG_AEC_CTRL00, mode); +} + +static int ov5640_get_hts(struct ov5640_dev *sensor) +{ + /* read HTS from register settings */ + u16 hts; + int ret; + + ret = ov5640_read_reg16(sensor, OV5640_REG_TIMING_HTS, &hts); + if (ret) + return ret; + return hts; +} + +static int ov5640_get_vts(struct ov5640_dev *sensor) +{ + u16 vts; + int ret; + + ret = ov5640_read_reg16(sensor, OV5640_REG_TIMING_VTS, &vts); + if (ret) + return ret; + return vts; +} + +static int ov5640_set_vts(struct ov5640_dev *sensor, int vts) +{ + return ov5640_write_reg16(sensor, OV5640_REG_TIMING_VTS, vts); +} + +static int ov5640_get_light_freq(struct ov5640_dev *sensor) +{ + /* get banding filter value */ + int ret, light_freq = 0; + u8 temp, temp1; + + ret = ov5640_read_reg(sensor, OV5640_REG_HZ5060_CTRL01, &temp); + if (ret) + return ret; + + if (temp & 0x80) { + /* manual */ + ret = ov5640_read_reg(sensor, OV5640_REG_HZ5060_CTRL00, + &temp1); + if (ret) + return ret; + if (temp1 & 0x04) { + /* 50Hz */ + light_freq = 50; + } else { + /* 60Hz */ + light_freq = 60; + } + } else { + /* auto */ + ret = ov5640_read_reg(sensor, OV5640_REG_SIGMADELTA_CTRL0C, + &temp1); + if (ret) + return ret; + + if (temp1 & 0x01) { + /* 50Hz */ + light_freq = 50; + } else { + /* 60Hz */ + } + } + + return light_freq; +} + +static int ov5640_set_bandingfilter(struct ov5640_dev *sensor) +{ + u32 band_step60, max_band60, band_step50, max_band50, prev_vts; + int ret; + + /* read preview PCLK */ + ret = ov5640_get_sysclk(sensor); + if (ret < 0) + return ret; + if (ret == 0) + return -EINVAL; + sensor->prev_sysclk = ret; + /* read preview HTS */ + ret = ov5640_get_hts(sensor); + if (ret < 0) + return ret; + if (ret == 0) + return -EINVAL; + sensor->prev_hts = ret; + + /* read preview VTS */ + ret = ov5640_get_vts(sensor); + if (ret < 0) + return ret; + prev_vts = ret; + + + /* calculate banding filter */ + /* 60Hz */ + band_step60 = sensor->prev_sysclk * 100 / sensor->prev_hts * 100 / 120; + ret = ov5640_write_reg16(sensor, OV5640_REG_AEC_B60_STEP, band_step60); + if (ret) + return ret; + if (!band_step60) + return -EINVAL; + max_band60 = (int)((prev_vts - 4) / band_step60); + ret = ov5640_write_reg(sensor, OV5640_REG_AEC_CTRL0D, max_band60); + if (ret) + return ret; + + /* 50Hz */ + band_step50 = sensor->prev_sysclk * 100 / sensor->prev_hts; + ret = ov5640_write_reg16(sensor, OV5640_REG_AEC_B50_STEP, band_step50); + if (ret) + return ret; + if (!band_step50) + return -EINVAL; + max_band50 = (int)((prev_vts - 4) / band_step50); + return ov5640_write_reg(sensor, OV5640_REG_AEC_CTRL0E, max_band50); +} + +static int ov5640_set_ae_target(struct ov5640_dev *sensor, int target) +{ + /* stable in high */ + u32 fast_high, fast_low; + int ret; + + sensor->ae_low = target * 23 / 25; /* 0.92 */ + sensor->ae_high = target * 27 / 25; /* 1.08 */ + + fast_high = sensor->ae_high << 1; + if (fast_high > 255) + fast_high = 255; + + fast_low = sensor->ae_low >> 1; + + ret = ov5640_write_reg(sensor, OV5640_REG_AEC_CTRL0F, sensor->ae_high); + if (ret) + return ret; + ret = ov5640_write_reg(sensor, OV5640_REG_AEC_CTRL10, sensor->ae_low); + if (ret) + return ret; + ret = ov5640_write_reg(sensor, OV5640_REG_AEC_CTRL1B, sensor->ae_high); + if (ret) + return ret; + ret = ov5640_write_reg(sensor, OV5640_REG_AEC_CTRL1E, sensor->ae_low); + if (ret) + return ret; + ret = ov5640_write_reg(sensor, OV5640_REG_AEC_CTRL11, fast_high); + if (ret) + return ret; + return ov5640_write_reg(sensor, OV5640_REG_AEC_CTRL1F, fast_low); +} + +static int ov5640_binning_on(struct ov5640_dev *sensor) +{ + u8 temp; + int ret; + + ret = ov5640_read_reg(sensor, OV5640_REG_TIMING_TC_REG21, &temp); + if (ret) + return ret; + temp &= 0xfe; + return temp ? 1 : 0; +} + +static int ov5640_set_virtual_channel(struct ov5640_dev *sensor) +{ + u8 temp, channel = virtual_channel; + int ret; + + if (channel > 3) + return -EINVAL; + + ret = ov5640_read_reg(sensor, OV5640_REG_DEBUG_MODE, &temp); + if (ret) + return ret; + temp &= ~(3 << 6); + temp |= (channel << 6); + return ov5640_write_reg(sensor, OV5640_REG_DEBUG_MODE, temp); +} + +static const struct ov5640_mode_info * +ov5640_find_mode(struct ov5640_dev *sensor, enum ov5640_frame_rate fr, + int width, int height, bool nearest) +{ + const struct ov5640_mode_info *mode = NULL; + int i; + + for (i = OV5640_NUM_MODES - 1; i >= 0; i--) { + mode = &ov5640_mode_data[fr][i]; + + if (!mode->reg_data) + continue; + + if ((nearest && mode->width <= width && + mode->height <= height) || + (!nearest && mode->width == width && + mode->height == height)) + break; + } + + if (nearest && i < 0) + mode = &ov5640_mode_data[fr][0]; + + return mode; +} + +/* + * sensor changes between scaling and subsampling, go through + * exposure calculation + */ +static int ov5640_set_mode_exposure_calc( + struct ov5640_dev *sensor, const struct ov5640_mode_info *mode) +{ + u32 prev_shutter, prev_gain16; + u32 cap_shutter, cap_gain16; + u32 cap_sysclk, cap_hts, cap_vts; + u32 light_freq, cap_bandfilt, cap_maxband; + u32 cap_gain16_shutter; + u8 average; + int ret; + + if (mode->reg_data == NULL) + return -EINVAL; + + /* read preview shutter */ + ret = ov5640_get_exposure(sensor); + if (ret < 0) + return ret; + prev_shutter = ret; + ret = ov5640_binning_on(sensor); + if (ret < 0) + return ret; + if (ret && mode->id != OV5640_MODE_720P_1280_720 && + mode->id != OV5640_MODE_1080P_1920_1080) + prev_shutter *= 2; + + /* read preview gain */ + ret = ov5640_get_gain(sensor); + if (ret < 0) + return ret; + prev_gain16 = ret; + + /* get average */ + ret = ov5640_read_reg(sensor, OV5640_REG_AVG_READOUT, &average); + if (ret) + return ret; + + /* turn off night mode for capture */ + ret = ov5640_set_night_mode(sensor); + if (ret < 0) + return ret; + + /* Write capture setting */ + ret = ov5640_load_regs(sensor, mode); + if (ret < 0) + return ret; + + /* read capture VTS */ + ret = ov5640_get_vts(sensor); + if (ret < 0) + return ret; + cap_vts = ret; + ret = ov5640_get_hts(sensor); + if (ret < 0) + return ret; + if (ret == 0) + return -EINVAL; + cap_hts = ret; + + ret = ov5640_get_sysclk(sensor); + if (ret < 0) + return ret; + if (ret == 0) + return -EINVAL; + cap_sysclk = ret; + + /* calculate capture banding filter */ + ret = ov5640_get_light_freq(sensor); + if (ret < 0) + return ret; + light_freq = ret; + + if (light_freq == 60) { + /* 60Hz */ + cap_bandfilt = cap_sysclk * 100 / cap_hts * 100 / 120; + } else { + /* 50Hz */ + cap_bandfilt = cap_sysclk * 100 / cap_hts; + } + + if (!sensor->prev_sysclk) { + ret = ov5640_get_sysclk(sensor); + if (ret < 0) + return ret; + if (ret == 0) + return -EINVAL; + sensor->prev_sysclk = ret; + } + + if (!cap_bandfilt) + return -EINVAL; + + cap_maxband = (int)((cap_vts - 4) / cap_bandfilt); + + /* calculate capture shutter/gain16 */ + if (average > sensor->ae_low && average < sensor->ae_high) { + /* in stable range */ + cap_gain16_shutter = + prev_gain16 * prev_shutter * + cap_sysclk / sensor->prev_sysclk * + sensor->prev_hts / cap_hts * + sensor->ae_target / average; + } else { + cap_gain16_shutter = + prev_gain16 * prev_shutter * + cap_sysclk / sensor->prev_sysclk * + sensor->prev_hts / cap_hts; + } + + /* gain to shutter */ + if (cap_gain16_shutter < (cap_bandfilt * 16)) { + /* shutter < 1/100 */ + cap_shutter = cap_gain16_shutter / 16; + if (cap_shutter < 1) + cap_shutter = 1; + + cap_gain16 = cap_gain16_shutter / cap_shutter; + if (cap_gain16 < 16) + cap_gain16 = 16; + } else { + if (cap_gain16_shutter > (cap_bandfilt * cap_maxband * 16)) { + /* exposure reach max */ + cap_shutter = cap_bandfilt * cap_maxband; + if (!cap_shutter) + return -EINVAL; + + cap_gain16 = cap_gain16_shutter / cap_shutter; + } else { + /* 1/100 < (cap_shutter = n/100) =< max */ + cap_shutter = + ((int)(cap_gain16_shutter / 16 / cap_bandfilt)) + * cap_bandfilt; + if (!cap_shutter) + return -EINVAL; + + cap_gain16 = cap_gain16_shutter / cap_shutter; + } + } + + /* set capture gain */ + ret = __v4l2_ctrl_s_ctrl(sensor->ctrls.gain, cap_gain16); + if (ret) + return ret; + + /* write capture shutter */ + if (cap_shutter > (cap_vts - 4)) { + cap_vts = cap_shutter + 4; + ret = ov5640_set_vts(sensor, cap_vts); + if (ret < 0) + return ret; + } + + /* set exposure */ + return __v4l2_ctrl_s_ctrl(sensor->ctrls.exposure, cap_shutter); +} + +/* + * if sensor changes inside scaling or subsampling + * change mode directly + */ +static int ov5640_set_mode_direct(struct ov5640_dev *sensor, + const struct ov5640_mode_info *mode) +{ + int ret; + + if (mode->reg_data == NULL) + return -EINVAL; + + /* Write capture setting */ + ret = ov5640_load_regs(sensor, mode); + if (ret < 0) + return ret; + + /* turn auto gain/exposure back on for direct mode */ + ret = __v4l2_ctrl_s_ctrl(sensor->ctrls.auto_gain, 1); + if (ret) + return ret; + return __v4l2_ctrl_s_ctrl(sensor->ctrls.auto_exp, V4L2_EXPOSURE_AUTO); +} + +static int ov5640_set_mode(struct ov5640_dev *sensor, + const struct ov5640_mode_info *orig_mode) +{ + const struct ov5640_mode_info *mode = sensor->current_mode; + enum ov5640_downsize_mode dn_mode, orig_dn_mode; + int ret; + + dn_mode = mode->dn_mode; + orig_dn_mode = orig_mode->dn_mode; + + /* auto gain and exposure must be turned off when changing modes */ + ret = __v4l2_ctrl_s_ctrl(sensor->ctrls.auto_gain, 0); + if (ret) + return ret; + ret = __v4l2_ctrl_s_ctrl(sensor->ctrls.auto_exp, V4L2_EXPOSURE_MANUAL); + if (ret) + return ret; + + if ((dn_mode == SUBSAMPLING && orig_dn_mode == SCALING) || + (dn_mode == SCALING && orig_dn_mode == SUBSAMPLING)) { + /* + * change between subsampling and scaling + * go through exposure calucation + */ + ret = ov5640_set_mode_exposure_calc(sensor, mode); + } else { + /* + * change inside subsampling or scaling + * download firmware directly + */ + ret = ov5640_set_mode_direct(sensor, mode); + } + + if (ret < 0) + return ret; + + ret = ov5640_set_ae_target(sensor, sensor->ae_target); + if (ret < 0) + return ret; + ret = ov5640_get_light_freq(sensor); + if (ret < 0) + return ret; + ret = ov5640_set_bandingfilter(sensor); + if (ret < 0) + return ret; + ret = ov5640_set_virtual_channel(sensor); + if (ret < 0) + return ret; + + sensor->pending_mode_change = false; + + return 0; +} + +/* restore the last set video mode after chip power-on */ +static int ov5640_restore_mode(struct ov5640_dev *sensor) +{ + int ret; + + /* first load the initial register values */ + ret = ov5640_load_regs(sensor, &ov5640_mode_init_data); + if (ret < 0) + return ret; + + /* now restore the last capture mode */ + return ov5640_set_mode(sensor, &ov5640_mode_init_data); +} + +static void ov5640_power(struct ov5640_dev *sensor, bool enable) +{ + if (sensor->pwdn_gpio) + gpiod_set_value(sensor->pwdn_gpio, enable ? 0 : 1); +} + +static void ov5640_reset(struct ov5640_dev *sensor) +{ + if (!sensor->reset_gpio) + return; + + gpiod_set_value(sensor->reset_gpio, 0); + + /* camera power cycle */ + ov5640_power(sensor, false); + usleep_range(5000, 10000); + ov5640_power(sensor, true); + usleep_range(5000, 10000); + + gpiod_set_value(sensor->reset_gpio, 1); + usleep_range(1000, 2000); + + gpiod_set_value(sensor->reset_gpio, 0); + usleep_range(5000, 10000); +} + +static int ov5640_set_power(struct ov5640_dev *sensor, bool on) +{ + int ret = 0; + + if (on) { + clk_prepare_enable(sensor->xclk); + + ret = regulator_bulk_enable(OV5640_NUM_SUPPLIES, + sensor->supplies); + if (ret) + goto xclk_off; + + ov5640_reset(sensor); + ov5640_power(sensor, true); + + ret = ov5640_init_slave_id(sensor); + if (ret) + goto power_off; + + ret = ov5640_restore_mode(sensor); + if (ret) + goto power_off; + + /* + * start streaming briefly followed by stream off in + * order to coax the clock lane into LP-11 state. + */ + ret = ov5640_set_stream(sensor, true); + if (ret) + goto power_off; + usleep_range(1000, 2000); + ret = ov5640_set_stream(sensor, false); + if (ret) + goto power_off; + + return 0; + } + +power_off: + ov5640_power(sensor, false); + regulator_bulk_disable(OV5640_NUM_SUPPLIES, sensor->supplies); +xclk_off: + clk_disable_unprepare(sensor->xclk); + return ret; +} + +/* --------------- Subdev Operations --------------- */ + +static int ov5640_s_power(struct v4l2_subdev *sd, int on) +{ + struct ov5640_dev *sensor = to_ov5640_dev(sd); + int ret = 0; + + mutex_lock(&sensor->lock); + + /* + * If the power count is modified from 0 to != 0 or from != 0 to 0, + * update the power state. + */ + if (sensor->power_count == !on) { + ret = ov5640_set_power(sensor, !!on); + if (ret) + goto out; + } + + /* Update the power count. */ + sensor->power_count += on ? 1 : -1; + WARN_ON(sensor->power_count < 0); +out: + mutex_unlock(&sensor->lock); + + if (on && !ret && sensor->power_count == 1) { + /* restore controls */ + ret = v4l2_ctrl_handler_setup(&sensor->ctrls.handler); + } + + return ret; +} + +static int ov5640_try_frame_interval(struct ov5640_dev *sensor, + struct v4l2_fract *fi, + u32 width, u32 height) +{ + const struct ov5640_mode_info *mode; + u32 minfps, maxfps, fps; + int ret; + + minfps = ov5640_framerates[OV5640_15_FPS]; + maxfps = ov5640_framerates[OV5640_30_FPS]; + + if (fi->numerator == 0) { + fi->denominator = maxfps; + fi->numerator = 1; + return OV5640_30_FPS; + } + + fps = DIV_ROUND_CLOSEST(fi->denominator, fi->numerator); + + fi->numerator = 1; + if (fps > maxfps) + fi->denominator = maxfps; + else if (fps < minfps) + fi->denominator = minfps; + else if (2 * fps >= 2 * minfps + (maxfps - minfps)) + fi->denominator = maxfps; + else + fi->denominator = minfps; + + ret = (fi->denominator == minfps) ? OV5640_15_FPS : OV5640_30_FPS; + + mode = ov5640_find_mode(sensor, ret, width, height, false); + return mode ? ret : -EINVAL; +} + +static int ov5640_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct ov5640_dev *sensor = to_ov5640_dev(sd); + struct v4l2_mbus_framefmt *fmt; + + if (format->pad != 0) + return -EINVAL; + + mutex_lock(&sensor->lock); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) + fmt = v4l2_subdev_get_try_format(&sensor->sd, cfg, + format->pad); + else + fmt = &sensor->fmt; + + format->format = *fmt; + + mutex_unlock(&sensor->lock); + + return 0; +} + +static int ov5640_try_fmt_internal(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *fmt, + enum ov5640_frame_rate fr, + const struct ov5640_mode_info **new_mode) +{ + struct ov5640_dev *sensor = to_ov5640_dev(sd); + const struct ov5640_mode_info *mode; + + mode = ov5640_find_mode(sensor, fr, fmt->width, fmt->height, true); + if (!mode) + return -EINVAL; + + fmt->width = mode->width; + fmt->height = mode->height; + fmt->code = sensor->fmt.code; + + if (new_mode) + *new_mode = mode; + return 0; +} + +static int ov5640_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct ov5640_dev *sensor = to_ov5640_dev(sd); + const struct ov5640_mode_info *new_mode; + int ret; + + if (format->pad != 0) + return -EINVAL; + + mutex_lock(&sensor->lock); + + if (sensor->streaming) { + ret = -EBUSY; + goto out; + } + + ret = ov5640_try_fmt_internal(sd, &format->format, + sensor->current_fr, &new_mode); + if (ret) + goto out; + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *fmt = + v4l2_subdev_get_try_format(sd, cfg, 0); + + *fmt = format->format; + goto out; + } + + sensor->current_mode = new_mode; + sensor->fmt = format->format; + sensor->pending_mode_change = true; +out: + mutex_unlock(&sensor->lock); + return ret; +} + + +/* + * Sensor Controls. + */ + +static int ov5640_set_ctrl_hue(struct ov5640_dev *sensor, int value) +{ + int ret; + + if (value) { + ret = ov5640_mod_reg(sensor, OV5640_REG_SDE_CTRL0, + BIT(0), BIT(0)); + if (ret) + return ret; + ret = ov5640_write_reg16(sensor, OV5640_REG_SDE_CTRL1, value); + } else { + ret = ov5640_mod_reg(sensor, OV5640_REG_SDE_CTRL0, BIT(0), 0); + } + + return ret; +} + +static int ov5640_set_ctrl_contrast(struct ov5640_dev *sensor, int value) +{ + int ret; + + if (value) { + ret = ov5640_mod_reg(sensor, OV5640_REG_SDE_CTRL0, + BIT(2), BIT(2)); + if (ret) + return ret; + ret = ov5640_write_reg(sensor, OV5640_REG_SDE_CTRL5, + value & 0xff); + } else { + ret = ov5640_mod_reg(sensor, OV5640_REG_SDE_CTRL0, BIT(2), 0); + } + + return ret; +} + +static int ov5640_set_ctrl_saturation(struct ov5640_dev *sensor, int value) +{ + int ret; + + if (value) { + ret = ov5640_mod_reg(sensor, OV5640_REG_SDE_CTRL0, + BIT(1), BIT(1)); + if (ret) + return ret; + ret = ov5640_write_reg(sensor, OV5640_REG_SDE_CTRL3, + value & 0xff); + if (ret) + return ret; + ret = ov5640_write_reg(sensor, OV5640_REG_SDE_CTRL4, + value & 0xff); + } else { + ret = ov5640_mod_reg(sensor, OV5640_REG_SDE_CTRL0, BIT(1), 0); + } + + return ret; +} + +static int ov5640_set_ctrl_white_balance(struct ov5640_dev *sensor, int awb) +{ + int ret; + + ret = ov5640_mod_reg(sensor, OV5640_REG_AWB_MANUAL_CTRL, + BIT(0), awb ? 0 : 1); + if (ret) + return ret; + + if (!awb) { + u16 red = (u16)sensor->ctrls.red_balance->val; + u16 blue = (u16)sensor->ctrls.blue_balance->val; + + ret = ov5640_write_reg16(sensor, OV5640_REG_AWB_R_GAIN, red); + if (ret) + return ret; + ret = ov5640_write_reg16(sensor, OV5640_REG_AWB_B_GAIN, blue); + } + + return ret; +} + +static int ov5640_set_ctrl_exposure(struct ov5640_dev *sensor, int exp) +{ + struct ov5640_ctrls *ctrls = &sensor->ctrls; + bool auto_exposure = (exp == V4L2_EXPOSURE_AUTO); + int ret = 0; + + if (ctrls->auto_exp->is_new) { + ret = ov5640_mod_reg(sensor, OV5640_REG_AEC_PK_MANUAL, + BIT(0), auto_exposure ? 0 : BIT(0)); + if (ret) + return ret; + } + + if (!auto_exposure && ctrls->exposure->is_new) { + u16 max_exp; + + ret = ov5640_read_reg16(sensor, OV5640_REG_AEC_PK_VTS, + &max_exp); + if (ret) + return ret; + ret = ov5640_get_vts(sensor); + if (ret < 0) + return ret; + max_exp += ret; + + if (ctrls->exposure->val < max_exp) + ret = ov5640_set_exposure(sensor, ctrls->exposure->val); + } + + return ret; +} + +static int ov5640_set_ctrl_gain(struct ov5640_dev *sensor, int auto_gain) +{ + struct ov5640_ctrls *ctrls = &sensor->ctrls; + int ret = 0; + + if (ctrls->auto_gain->is_new) { + ret = ov5640_mod_reg(sensor, OV5640_REG_AEC_PK_MANUAL, + BIT(1), ctrls->auto_gain->val ? 0 : BIT(1)); + if (ret) + return ret; + } + + if (!auto_gain && ctrls->gain->is_new) { + u16 gain = (u16)ctrls->gain->val; + + ret = ov5640_write_reg16(sensor, OV5640_REG_AEC_PK_REAL_GAIN, + gain & 0x3ff); + } + + return ret; +} + +static int ov5640_set_ctrl_test_pattern(struct ov5640_dev *sensor, int value) +{ + return ov5640_mod_reg(sensor, OV5640_REG_PRE_ISP_TEST_SET1, + 0xa4, value ? 0xa4 : 0); +} + +static int ov5640_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = ctrl_to_sd(ctrl); + struct ov5640_dev *sensor = to_ov5640_dev(sd); + int val; + + /* v4l2_ctrl_lock() locks our own mutex */ + + switch (ctrl->id) { + case V4L2_CID_AUTOGAIN: + if (!ctrl->val) + return 0; + val = ov5640_get_gain(sensor); + if (val < 0) + return val; + sensor->ctrls.gain->val = val; + break; + case V4L2_CID_EXPOSURE_AUTO: + if (ctrl->val == V4L2_EXPOSURE_MANUAL) + return 0; + val = ov5640_get_exposure(sensor); + if (val < 0) + return val; + sensor->ctrls.exposure->val = val; + break; + } + + return 0; +} + +static int ov5640_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = ctrl_to_sd(ctrl); + struct ov5640_dev *sensor = to_ov5640_dev(sd); + int ret; + + /* v4l2_ctrl_lock() locks our own mutex */ + + /* + * If the device is not powered up by the host driver do + * not apply any controls to H/W at this time. Instead + * the controls will be restored right after power-up. + */ + if (sensor->power_count == 0) + return 0; + + switch (ctrl->id) { + case V4L2_CID_AUTOGAIN: + ret = ov5640_set_ctrl_gain(sensor, ctrl->val); + break; + case V4L2_CID_EXPOSURE_AUTO: + ret = ov5640_set_ctrl_exposure(sensor, ctrl->val); + break; + case V4L2_CID_AUTO_WHITE_BALANCE: + ret = ov5640_set_ctrl_white_balance(sensor, ctrl->val); + break; + case V4L2_CID_HUE: + ret = ov5640_set_ctrl_hue(sensor, ctrl->val); + break; + case V4L2_CID_CONTRAST: + ret = ov5640_set_ctrl_contrast(sensor, ctrl->val); + break; + case V4L2_CID_SATURATION: + ret = ov5640_set_ctrl_saturation(sensor, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: + ret = ov5640_set_ctrl_test_pattern(sensor, ctrl->val); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static const struct v4l2_ctrl_ops ov5640_ctrl_ops = { + .g_volatile_ctrl = ov5640_g_volatile_ctrl, + .s_ctrl = ov5640_s_ctrl, +}; + +static const char * const test_pattern_menu[] = { + "Disabled", + "Color bars", +}; + +static int ov5640_init_controls(struct ov5640_dev *sensor) +{ + const struct v4l2_ctrl_ops *ops = &ov5640_ctrl_ops; + struct ov5640_ctrls *ctrls = &sensor->ctrls; + struct v4l2_ctrl_handler *hdl = &ctrls->handler; + int ret; + + v4l2_ctrl_handler_init(hdl, 32); + + /* we can use our own mutex for the ctrl lock */ + hdl->lock = &sensor->lock; + + /* Auto/manual white balance */ + ctrls->auto_wb = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_AUTO_WHITE_BALANCE, + 0, 1, 1, 1); + ctrls->blue_balance = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BLUE_BALANCE, + 0, 4095, 1, 0); + ctrls->red_balance = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_RED_BALANCE, + 0, 4095, 1, 0); + /* Auto/manual exposure */ + ctrls->auto_exp = v4l2_ctrl_new_std_menu(hdl, ops, + V4L2_CID_EXPOSURE_AUTO, + V4L2_EXPOSURE_MANUAL, 0, + V4L2_EXPOSURE_AUTO); + ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, + 0, 65535, 1, 0); + /* Auto/manual gain */ + ctrls->auto_gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_AUTOGAIN, + 0, 1, 1, 1); + ctrls->gain = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_GAIN, + 0, 1023, 1, 0); + + ctrls->saturation = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_SATURATION, + 0, 255, 1, 64); + ctrls->hue = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HUE, + 0, 359, 1, 0); + ctrls->contrast = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_CONTRAST, + 0, 255, 1, 0); + ctrls->test_pattern = + v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(test_pattern_menu) - 1, + 0, 0, test_pattern_menu); + + if (hdl->error) { + ret = hdl->error; + goto free_ctrls; + } + + ctrls->gain->flags |= V4L2_CTRL_FLAG_VOLATILE; + ctrls->exposure->flags |= V4L2_CTRL_FLAG_VOLATILE; + + v4l2_ctrl_auto_cluster(3, &ctrls->auto_wb, 0, false); + v4l2_ctrl_auto_cluster(2, &ctrls->auto_gain, 0, true); + v4l2_ctrl_auto_cluster(2, &ctrls->auto_exp, 1, true); + + sensor->sd.ctrl_handler = hdl; + return 0; + +free_ctrls: + v4l2_ctrl_handler_free(hdl); + return ret; +} + +static int ov5640_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->pad != 0) + return -EINVAL; + if (fse->index >= OV5640_NUM_MODES) + return -EINVAL; + + fse->min_width = fse->max_width = + ov5640_mode_data[0][fse->index].width; + fse->min_height = fse->max_height = + ov5640_mode_data[0][fse->index].height; + + return 0; +} + +static int ov5640_enum_frame_interval( + struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_interval_enum *fie) +{ + struct ov5640_dev *sensor = to_ov5640_dev(sd); + struct v4l2_fract tpf; + int ret; + + if (fie->pad != 0) + return -EINVAL; + if (fie->index >= OV5640_NUM_FRAMERATES) + return -EINVAL; + + tpf.numerator = 1; + tpf.denominator = ov5640_framerates[fie->index]; + + ret = ov5640_try_frame_interval(sensor, &tpf, + fie->width, fie->height); + if (ret < 0) + return -EINVAL; + + fie->interval = tpf; + return 0; +} + +static int ov5640_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct ov5640_dev *sensor = to_ov5640_dev(sd); + + mutex_lock(&sensor->lock); + fi->interval = sensor->frame_interval; + mutex_unlock(&sensor->lock); + + return 0; +} + +static int ov5640_s_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct ov5640_dev *sensor = to_ov5640_dev(sd); + const struct ov5640_mode_info *mode; + int frame_rate, ret = 0; + + if (fi->pad != 0) + return -EINVAL; + + mutex_lock(&sensor->lock); + + if (sensor->streaming) { + ret = -EBUSY; + goto out; + } + + mode = sensor->current_mode; + + frame_rate = ov5640_try_frame_interval(sensor, &fi->interval, + mode->width, mode->height); + if (frame_rate < 0) + frame_rate = OV5640_15_FPS; + + sensor->current_fr = frame_rate; + sensor->frame_interval = fi->interval; + sensor->pending_mode_change = true; +out: + mutex_unlock(&sensor->lock); + return ret; +} + +static int ov5640_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct ov5640_dev *sensor = to_ov5640_dev(sd); + + if (code->pad != 0) + return -EINVAL; + if (code->index != 0) + return -EINVAL; + + code->code = sensor->fmt.code; + + return 0; +} + +static int ov5640_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct ov5640_dev *sensor = to_ov5640_dev(sd); + int ret = 0; + + mutex_lock(&sensor->lock); + + if (sensor->streaming == !enable) { + if (enable && sensor->pending_mode_change) { + ret = ov5640_set_mode(sensor, sensor->current_mode); + if (ret) + goto out; + } + + ret = ov5640_set_stream(sensor, enable); + if (!ret) + sensor->streaming = enable; + } +out: + mutex_unlock(&sensor->lock); + return ret; +} + +static const struct v4l2_subdev_core_ops ov5640_core_ops = { + .s_power = ov5640_s_power, +}; + +static const struct v4l2_subdev_video_ops ov5640_video_ops = { + .g_frame_interval = ov5640_g_frame_interval, + .s_frame_interval = ov5640_s_frame_interval, + .s_stream = ov5640_s_stream, +}; + +static const struct v4l2_subdev_pad_ops ov5640_pad_ops = { + .enum_mbus_code = ov5640_enum_mbus_code, + .get_fmt = ov5640_get_fmt, + .set_fmt = ov5640_set_fmt, + .enum_frame_size = ov5640_enum_frame_size, + .enum_frame_interval = ov5640_enum_frame_interval, +}; + +static const struct v4l2_subdev_ops ov5640_subdev_ops = { + .core = &ov5640_core_ops, + .video = &ov5640_video_ops, + .pad = &ov5640_pad_ops, +}; + +static int ov5640_get_regulators(struct ov5640_dev *sensor) +{ + int i; + + for (i = 0; i < OV5640_NUM_SUPPLIES; i++) + sensor->supplies[i].supply = ov5640_supply_name[i]; + + return devm_regulator_bulk_get(&sensor->i2c_client->dev, + OV5640_NUM_SUPPLIES, + sensor->supplies); +} + +static int ov5640_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct fwnode_handle *endpoint; + struct ov5640_dev *sensor; + int ret; + + sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); + if (!sensor) + return -ENOMEM; + + sensor->i2c_client = client; + sensor->fmt.code = MEDIA_BUS_FMT_UYVY8_2X8; + sensor->fmt.width = 640; + sensor->fmt.height = 480; + sensor->fmt.field = V4L2_FIELD_NONE; + sensor->frame_interval.numerator = 1; + sensor->frame_interval.denominator = ov5640_framerates[OV5640_30_FPS]; + sensor->current_fr = OV5640_30_FPS; + sensor->current_mode = + &ov5640_mode_data[OV5640_30_FPS][OV5640_MODE_VGA_640_480]; + sensor->pending_mode_change = true; + + sensor->ae_target = 52; + + endpoint = fwnode_graph_get_next_endpoint( + of_fwnode_handle(client->dev.of_node), NULL); + if (!endpoint) { + dev_err(dev, "endpoint node not found\n"); + return -EINVAL; + } + + ret = v4l2_fwnode_endpoint_parse(endpoint, &sensor->ep); + fwnode_handle_put(endpoint); + if (ret) { + dev_err(dev, "Could not parse endpoint\n"); + return ret; + } + + if (sensor->ep.bus_type != V4L2_MBUS_CSI2) { + dev_err(dev, "invalid bus type, must be MIPI CSI2\n"); + return -EINVAL; + } + + /* get system clock (xclk) */ + sensor->xclk = devm_clk_get(dev, "xclk"); + if (IS_ERR(sensor->xclk)) { + dev_err(dev, "failed to get xclk\n"); + return PTR_ERR(sensor->xclk); + } + + sensor->xclk_freq = clk_get_rate(sensor->xclk); + if (sensor->xclk_freq < OV5640_XCLK_MIN || + sensor->xclk_freq > OV5640_XCLK_MAX) { + dev_err(dev, "xclk frequency out of range: %d Hz\n", + sensor->xclk_freq); + return -EINVAL; + } + + /* request optional power down pin */ + sensor->pwdn_gpio = devm_gpiod_get_optional(dev, "powerdown", + GPIOD_OUT_HIGH); + /* request optional reset pin */ + sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + + v4l2_i2c_subdev_init(&sensor->sd, client, &ov5640_subdev_ops); + + sensor->sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE; + sensor->pad.flags = MEDIA_PAD_FL_SOURCE; + sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad); + if (ret) + return ret; + + ret = ov5640_get_regulators(sensor); + if (ret) + return ret; + + mutex_init(&sensor->lock); + + ret = ov5640_init_controls(sensor); + if (ret) + goto entity_cleanup; + + ret = v4l2_async_register_subdev(&sensor->sd); + if (ret) + goto free_ctrls; + + return 0; + +free_ctrls: + v4l2_ctrl_handler_free(&sensor->ctrls.handler); +entity_cleanup: + mutex_destroy(&sensor->lock); + media_entity_cleanup(&sensor->sd.entity); + return ret; +} + +static int ov5640_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov5640_dev *sensor = to_ov5640_dev(sd); + + v4l2_async_unregister_subdev(&sensor->sd); + mutex_destroy(&sensor->lock); + media_entity_cleanup(&sensor->sd.entity); + v4l2_ctrl_handler_free(&sensor->ctrls.handler); + + return 0; +} + +static const struct i2c_device_id ov5640_id[] = { + {"ov5640", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, ov5640_id); + +static const struct of_device_id ov5640_dt_ids[] = { + { .compatible = "ovti,ov5640" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ov5640_dt_ids); + +static struct i2c_driver ov5640_i2c_driver = { + .driver = { + .name = "ov5640", + .of_match_table = ov5640_dt_ids, + }, + .id_table = ov5640_id, + .probe = ov5640_probe, + .remove = ov5640_remove, +}; + +module_i2c_driver(ov5640_i2c_driver); + +MODULE_DESCRIPTION("OV5640 MIPI Camera Subdev Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/ov5645.c b/drivers/media/i2c/ov5645.c index 57bd591ea54b..d1e844f7f03f 100644 --- a/drivers/media/i2c/ov5645.c +++ b/drivers/media/i2c/ov5645.c @@ -39,7 +39,7 @@ #include <linux/slab.h> #include <linux/types.h> #include <media/v4l2-ctrls.h> -#include <media/v4l2-of.h> +#include <media/v4l2-fwnode.h> #include <media/v4l2-subdev.h> #define OV5645_VOLTAGE_ANALOG 2800000 @@ -87,7 +87,7 @@ struct ov5645 { struct device *dev; struct v4l2_subdev sd; struct media_pad pad; - struct v4l2_of_endpoint ep; + struct v4l2_fwnode_endpoint ep; struct v4l2_mbus_framefmt fmt; struct v4l2_rect crop; struct clk *xclk; @@ -1102,7 +1102,8 @@ static int ov5645_probe(struct i2c_client *client, return -EINVAL; } - ret = v4l2_of_parse_endpoint(endpoint, &ov5645->ep); + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(endpoint), + &ov5645->ep); if (ret < 0) { dev_err(dev, "parsing endpoint node failed\n"); return ret; diff --git a/drivers/media/i2c/ov5647.c b/drivers/media/i2c/ov5647.c index f57a0b354cf6..95ce90fdb876 100644 --- a/drivers/media/i2c/ov5647.c +++ b/drivers/media/i2c/ov5647.c @@ -25,12 +25,13 @@ #include <linux/init.h> #include <linux/io.h> #include <linux/module.h> +#include <linux/of_graph.h> #include <linux/slab.h> #include <linux/videodev2.h> #include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> #include <media/v4l2-image-sizes.h> #include <media/v4l2-mediabus.h> -#include <media/v4l2-of.h> #define SENSOR_NAME "ov5647" @@ -510,7 +511,7 @@ static const struct v4l2_subdev_internal_ops ov5647_subdev_internal_ops = { static int ov5647_parse_dt(struct device_node *np) { - struct v4l2_of_endpoint bus_cfg; + struct v4l2_fwnode_endpoint bus_cfg; struct device_node *ep; int ret; @@ -519,7 +520,7 @@ static int ov5647_parse_dt(struct device_node *np) if (!ep) return -EINVAL; - ret = v4l2_of_parse_endpoint(ep, &bus_cfg); + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &bus_cfg); of_node_put(ep); return ret; diff --git a/drivers/media/i2c/s5c73m3/s5c73m3-core.c b/drivers/media/i2c/s5c73m3/s5c73m3-core.c index 3844853ab0a0..f434fb2ee6fc 100644 --- a/drivers/media/i2c/s5c73m3/s5c73m3-core.c +++ b/drivers/media/i2c/s5c73m3/s5c73m3-core.c @@ -24,6 +24,7 @@ #include <linux/media.h> #include <linux/module.h> #include <linux/of_gpio.h> +#include <linux/of_graph.h> #include <linux/regulator/consumer.h> #include <linux/sizes.h> #include <linux/slab.h> @@ -35,7 +36,7 @@ #include <media/v4l2-subdev.h> #include <media/v4l2-mediabus.h> #include <media/i2c/s5c73m3.h> -#include <media/v4l2-of.h> +#include <media/v4l2-fwnode.h> #include "s5c73m3.h" @@ -1602,7 +1603,7 @@ static int s5c73m3_get_platform_data(struct s5c73m3 *state) const struct s5c73m3_platform_data *pdata = dev->platform_data; struct device_node *node = dev->of_node; struct device_node *node_ep; - struct v4l2_of_endpoint ep; + struct v4l2_fwnode_endpoint ep; int ret; if (!node) { @@ -1639,7 +1640,7 @@ static int s5c73m3_get_platform_data(struct s5c73m3 *state) return 0; } - ret = v4l2_of_parse_endpoint(node_ep, &ep); + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(node_ep), &ep); of_node_put(node_ep); if (ret) return ret; diff --git a/drivers/media/i2c/s5k5baf.c b/drivers/media/i2c/s5k5baf.c index db82ed05792e..962051b9939d 100644 --- a/drivers/media/i2c/s5k5baf.c +++ b/drivers/media/i2c/s5k5baf.c @@ -30,7 +30,7 @@ #include <media/v4l2-device.h> #include <media/v4l2-subdev.h> #include <media/v4l2-mediabus.h> -#include <media/v4l2-of.h> +#include <media/v4l2-fwnode.h> static int debug; module_param(debug, int, 0644); @@ -1841,7 +1841,7 @@ static int s5k5baf_parse_device_node(struct s5k5baf *state, struct device *dev) { struct device_node *node = dev->of_node; struct device_node *node_ep; - struct v4l2_of_endpoint ep; + struct v4l2_fwnode_endpoint ep; int ret; if (!node) { @@ -1868,7 +1868,7 @@ static int s5k5baf_parse_device_node(struct s5k5baf *state, struct device *dev) return -EINVAL; } - ret = v4l2_of_parse_endpoint(node_ep, &ep); + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(node_ep), &ep); of_node_put(node_ep); if (ret) return ret; diff --git a/drivers/media/i2c/s5k6aa.c b/drivers/media/i2c/s5k6aa.c index faee11383cb7..9fd254a8e20d 100644 --- a/drivers/media/i2c/s5k6aa.c +++ b/drivers/media/i2c/s5k6aa.c @@ -838,7 +838,7 @@ static int __s5k6aa_power_on(struct s5k6aa *s5k6aa) if (s5k6aa->s_power) ret = s5k6aa->s_power(1); - usleep_range(4000, 4000); + usleep_range(4000, 5000); if (s5k6aa_gpio_deassert(s5k6aa, RST)) msleep(20); diff --git a/drivers/media/i2c/smiapp/Kconfig b/drivers/media/i2c/smiapp/Kconfig index 3149cda1d0db..f59718d8e51e 100644 --- a/drivers/media/i2c/smiapp/Kconfig +++ b/drivers/media/i2c/smiapp/Kconfig @@ -3,5 +3,6 @@ config VIDEO_SMIAPP depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API && HAVE_CLK depends on MEDIA_CAMERA_SUPPORT select VIDEO_SMIAPP_PLL + select V4L2_FWNODE ---help--- This is a generic driver for SMIA++/SMIA camera modules. diff --git a/drivers/media/i2c/smiapp/smiapp-core.c b/drivers/media/i2c/smiapp/smiapp-core.c index f4e92bdfe192..e0b0c032c4ac 100644 --- a/drivers/media/i2c/smiapp/smiapp-core.c +++ b/drivers/media/i2c/smiapp/smiapp-core.c @@ -27,12 +27,13 @@ #include <linux/gpio/consumer.h> #include <linux/module.h> #include <linux/pm_runtime.h> +#include <linux/property.h> #include <linux/regulator/consumer.h> #include <linux/slab.h> #include <linux/smiapp.h> #include <linux/v4l2-mediabus.h> +#include <media/v4l2-fwnode.h> #include <media/v4l2-device.h> -#include <media/v4l2-of.h> #include "smiapp.h" @@ -2784,19 +2785,20 @@ static int __maybe_unused smiapp_resume(struct device *dev) static struct smiapp_hwconfig *smiapp_get_hwconfig(struct device *dev) { struct smiapp_hwconfig *hwcfg; - struct v4l2_of_endpoint *bus_cfg; - struct device_node *ep; + struct v4l2_fwnode_endpoint *bus_cfg; + struct fwnode_handle *ep; + struct fwnode_handle *fwnode = dev_fwnode(dev); int i; int rval; - if (!dev->of_node) + if (!fwnode) return dev->platform_data; - ep = of_graph_get_next_endpoint(dev->of_node, NULL); + ep = fwnode_graph_get_next_endpoint(fwnode, NULL); if (!ep) return NULL; - bus_cfg = v4l2_of_alloc_parse_endpoint(ep); + bus_cfg = v4l2_fwnode_endpoint_alloc_parse(ep); if (IS_ERR(bus_cfg)) goto out_err; @@ -2817,11 +2819,10 @@ static struct smiapp_hwconfig *smiapp_get_hwconfig(struct device *dev) dev_dbg(dev, "lanes %u\n", hwcfg->lanes); /* NVM size is not mandatory */ - of_property_read_u32(dev->of_node, "nokia,nvm-size", - &hwcfg->nvm_size); + fwnode_property_read_u32(fwnode, "nokia,nvm-size", &hwcfg->nvm_size); - rval = of_property_read_u32(dev->of_node, "clock-frequency", - &hwcfg->ext_clk); + rval = fwnode_property_read_u32(fwnode, "clock-frequency", + &hwcfg->ext_clk); if (rval) { dev_warn(dev, "can't get clock-frequency\n"); goto out_err; @@ -2846,13 +2847,13 @@ static struct smiapp_hwconfig *smiapp_get_hwconfig(struct device *dev) dev_dbg(dev, "freq %d: %lld\n", i, hwcfg->op_sys_clock[i]); } - v4l2_of_free_endpoint(bus_cfg); - of_node_put(ep); + v4l2_fwnode_endpoint_free(bus_cfg); + fwnode_handle_put(ep); return hwcfg; out_err: - v4l2_of_free_endpoint(bus_cfg); - of_node_put(ep); + v4l2_fwnode_endpoint_free(bus_cfg); + fwnode_handle_put(ep); return NULL; } diff --git a/drivers/media/i2c/soc_camera/ov6650.c b/drivers/media/i2c/soc_camera/ov6650.c index dbd6d92c589f..d2be64d54b22 100644 --- a/drivers/media/i2c/soc_camera/ov6650.c +++ b/drivers/media/i2c/soc_camera/ov6650.c @@ -709,6 +709,7 @@ static int ov6650_set_fmt(struct v4l2_subdev *sd, switch (mf->code) { case MEDIA_BUS_FMT_Y10_1X10: mf->code = MEDIA_BUS_FMT_Y8_1X8; + /* fall through */ case MEDIA_BUS_FMT_Y8_1X8: case MEDIA_BUS_FMT_YVYU8_2X8: case MEDIA_BUS_FMT_YUYV8_2X8: @@ -718,6 +719,7 @@ static int ov6650_set_fmt(struct v4l2_subdev *sd, break; default: mf->code = MEDIA_BUS_FMT_SBGGR8_1X8; + /* fall through */ case MEDIA_BUS_FMT_SBGGR8_1X8: mf->colorspace = V4L2_COLORSPACE_SRGB; break; diff --git a/drivers/media/i2c/tc358743.c b/drivers/media/i2c/tc358743.c index 3251cba89e8f..8c54013984ed 100644 --- a/drivers/media/i2c/tc358743.c +++ b/drivers/media/i2c/tc358743.c @@ -33,6 +33,8 @@ #include <linux/delay.h> #include <linux/gpio/consumer.h> #include <linux/interrupt.h> +#include <linux/timer.h> +#include <linux/of_graph.h> #include <linux/videodev2.h> #include <linux/workqueue.h> #include <linux/v4l2-dv-timings.h> @@ -41,7 +43,7 @@ #include <media/v4l2-device.h> #include <media/v4l2-ctrls.h> #include <media/v4l2-event.h> -#include <media/v4l2-of.h> +#include <media/v4l2-fwnode.h> #include <media/i2c/tc358743.h> #include "tc358743_regs.h" @@ -61,6 +63,8 @@ MODULE_LICENSE("GPL"); #define I2C_MAX_XFER_SIZE (EDID_BLOCK_SIZE + 2) +#define POLL_INTERVAL_MS 1000 + static const struct v4l2_dv_timings_cap tc358743_timings_cap = { .type = V4L2_DV_BT_656_1120, /* keep this initialization for compatibility with GCC < 4.4.6 */ @@ -76,7 +80,7 @@ static const struct v4l2_dv_timings_cap tc358743_timings_cap = { struct tc358743_state { struct tc358743_platform_data pdata; - struct v4l2_of_bus_mipi_csi2 bus; + struct v4l2_fwnode_bus_mipi_csi2 bus; struct v4l2_subdev sd; struct media_pad pad; struct v4l2_ctrl_handler hdl; @@ -91,6 +95,9 @@ struct tc358743_state { struct delayed_work delayed_work_enable_hotplug; + struct timer_list timer; + struct work_struct work_i2c_poll; + /* edid */ u8 edid_blocks_written; @@ -1319,6 +1326,24 @@ static irqreturn_t tc358743_irq_handler(int irq, void *dev_id) return handled ? IRQ_HANDLED : IRQ_NONE; } +static void tc358743_irq_poll_timer(unsigned long arg) +{ + struct tc358743_state *state = (struct tc358743_state *)arg; + + schedule_work(&state->work_i2c_poll); + + mod_timer(&state->timer, jiffies + msecs_to_jiffies(POLL_INTERVAL_MS)); +} + +static void tc358743_work_i2c_poll(struct work_struct *work) +{ + struct tc358743_state *state = container_of(work, + struct tc358743_state, work_i2c_poll); + bool handled; + + tc358743_isr(&state->sd, 0, &handled); +} + static int tc358743_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh, struct v4l2_event_subscription *sub) { @@ -1473,6 +1498,23 @@ static int tc358743_s_stream(struct v4l2_subdev *sd, int enable) /* --------------- PAD OPS --------------- */ +static int tc358743_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + switch (code->index) { + case 0: + code->code = MEDIA_BUS_FMT_RGB888_1X24; + break; + case 1: + code->code = MEDIA_BUS_FMT_UYVY8_1X16; + break; + default: + return -EINVAL; + } + return 0; +} + static int tc358743_get_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_format *format) @@ -1642,6 +1684,7 @@ static const struct v4l2_subdev_video_ops tc358743_video_ops = { }; static const struct v4l2_subdev_pad_ops tc358743_pad_ops = { + .enum_mbus_code = tc358743_enum_mbus_code, .set_fmt = tc358743_set_fmt, .get_fmt = tc358743_get_fmt, .get_edid = tc358743_g_edid, @@ -1695,7 +1738,7 @@ static void tc358743_gpio_reset(struct tc358743_state *state) static int tc358743_probe_of(struct tc358743_state *state) { struct device *dev = &state->i2c_client->dev; - struct v4l2_of_endpoint *endpoint; + struct v4l2_fwnode_endpoint *endpoint; struct device_node *ep; struct clk *refclk; u32 bps_pr_lane; @@ -1715,7 +1758,7 @@ static int tc358743_probe_of(struct tc358743_state *state) return -EINVAL; } - endpoint = v4l2_of_alloc_parse_endpoint(ep); + endpoint = v4l2_fwnode_endpoint_alloc_parse(of_fwnode_handle(ep)); if (IS_ERR(endpoint)) { dev_err(dev, "failed to parse endpoint\n"); return PTR_ERR(endpoint); @@ -1730,7 +1773,11 @@ static int tc358743_probe_of(struct tc358743_state *state) state->bus = endpoint->bus.mipi_csi2; - clk_prepare_enable(refclk); + ret = clk_prepare_enable(refclk); + if (ret) { + dev_err(dev, "Failed! to enable clock\n"); + goto free_endpoint; + } state->pdata.refclk_hz = clk_get_rate(refclk); state->pdata.ddc5v_delay = DDC5V_DELAY_100_MS; @@ -1803,7 +1850,7 @@ static int tc358743_probe_of(struct tc358743_state *state) disable_clk: clk_disable_unprepare(refclk); free_endpoint: - v4l2_of_free_endpoint(endpoint); + v4l2_fwnode_endpoint_free(endpoint); return ret; } #else @@ -1887,6 +1934,8 @@ static int tc358743_probe(struct i2c_client *client, if (err < 0) goto err_hdl; + state->mbus_fmt_code = MEDIA_BUS_FMT_RGB888_1X24; + sd->dev = &client->dev; err = v4l2_async_register_subdev(sd); if (err < 0) @@ -1901,7 +1950,6 @@ static int tc358743_probe(struct i2c_client *client, tc358743_s_dv_timings(sd, &default_timing); - state->mbus_fmt_code = MEDIA_BUS_FMT_RGB888_1X24; tc358743_set_csi_color_space(sd); tc358743_init_interrupts(sd); @@ -1914,6 +1962,14 @@ static int tc358743_probe(struct i2c_client *client, "tc358743", state); if (err) goto err_work_queues; + } else { + INIT_WORK(&state->work_i2c_poll, + tc358743_work_i2c_poll); + state->timer.data = (unsigned long)state; + state->timer.function = tc358743_irq_poll_timer; + state->timer.expires = jiffies + + msecs_to_jiffies(POLL_INTERVAL_MS); + add_timer(&state->timer); } tc358743_enable_interrupts(sd, tx_5v_power_present(sd)); @@ -1929,6 +1985,8 @@ static int tc358743_probe(struct i2c_client *client, return 0; err_work_queues: + if (!state->i2c_client->irq) + flush_work(&state->work_i2c_poll); cancel_delayed_work(&state->delayed_work_enable_hotplug); mutex_destroy(&state->confctl_mutex); err_hdl: @@ -1942,6 +2000,10 @@ static int tc358743_remove(struct i2c_client *client) struct v4l2_subdev *sd = i2c_get_clientdata(client); struct tc358743_state *state = to_state(sd); + if (!state->i2c_client->irq) { + del_timer_sync(&state->timer); + flush_work(&state->work_i2c_poll); + } cancel_delayed_work(&state->delayed_work_enable_hotplug); v4l2_async_unregister_subdev(sd); v4l2_device_unregister_subdev(sd); diff --git a/drivers/media/i2c/tvp514x.c b/drivers/media/i2c/tvp514x.c index 07853d2252aa..ad2df998f9c5 100644 --- a/drivers/media/i2c/tvp514x.c +++ b/drivers/media/i2c/tvp514x.c @@ -38,7 +38,7 @@ #include <media/v4l2-device.h> #include <media/v4l2-common.h> #include <media/v4l2-mediabus.h> -#include <media/v4l2-of.h> +#include <media/v4l2-fwnode.h> #include <media/v4l2-ctrls.h> #include <media/i2c/tvp514x.h> #include <media/media-entity.h> @@ -998,7 +998,7 @@ static struct tvp514x_platform_data * tvp514x_get_pdata(struct i2c_client *client) { struct tvp514x_platform_data *pdata = NULL; - struct v4l2_of_endpoint bus_cfg; + struct v4l2_fwnode_endpoint bus_cfg; struct device_node *endpoint; unsigned int flags; @@ -1009,7 +1009,7 @@ tvp514x_get_pdata(struct i2c_client *client) if (!endpoint) return NULL; - if (v4l2_of_parse_endpoint(endpoint, &bus_cfg)) + if (v4l2_fwnode_endpoint_parse(of_fwnode_handle(endpoint), &bus_cfg)) goto done; pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL); diff --git a/drivers/media/i2c/tvp5150.c b/drivers/media/i2c/tvp5150.c index 04e96b3057bb..9da4bf4f2c7a 100644 --- a/drivers/media/i2c/tvp5150.c +++ b/drivers/media/i2c/tvp5150.c @@ -12,10 +12,11 @@ #include <linux/delay.h> #include <linux/gpio/consumer.h> #include <linux/module.h> +#include <linux/of_graph.h> #include <media/v4l2-async.h> #include <media/v4l2-device.h> #include <media/v4l2-ctrls.h> -#include <media/v4l2-of.h> +#include <media/v4l2-fwnode.h> #include <media/v4l2-mc.h> #include "tvp5150_reg.h" @@ -1358,7 +1359,7 @@ static int tvp5150_init(struct i2c_client *c) static int tvp5150_parse_dt(struct tvp5150 *decoder, struct device_node *np) { - struct v4l2_of_endpoint bus_cfg; + struct v4l2_fwnode_endpoint bus_cfg; struct device_node *ep; #ifdef CONFIG_MEDIA_CONTROLLER struct device_node *connectors, *child; @@ -1373,7 +1374,7 @@ static int tvp5150_parse_dt(struct tvp5150 *decoder, struct device_node *np) if (!ep) return -EINVAL; - ret = v4l2_of_parse_endpoint(ep, &bus_cfg); + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &bus_cfg); if (ret) goto err; diff --git a/drivers/media/i2c/tvp7002.c b/drivers/media/i2c/tvp7002.c index 4c1190127c85..a26c1a3f7183 100644 --- a/drivers/media/i2c/tvp7002.c +++ b/drivers/media/i2c/tvp7002.c @@ -33,7 +33,7 @@ #include <media/v4l2-device.h> #include <media/v4l2-common.h> #include <media/v4l2-ctrls.h> -#include <media/v4l2-of.h> +#include <media/v4l2-fwnode.h> #include "tvp7002_reg.h" @@ -889,7 +889,7 @@ static const struct v4l2_subdev_ops tvp7002_ops = { static struct tvp7002_config * tvp7002_get_pdata(struct i2c_client *client) { - struct v4l2_of_endpoint bus_cfg; + struct v4l2_fwnode_endpoint bus_cfg; struct tvp7002_config *pdata = NULL; struct device_node *endpoint; unsigned int flags; @@ -901,7 +901,7 @@ tvp7002_get_pdata(struct i2c_client *client) if (!endpoint) return NULL; - if (v4l2_of_parse_endpoint(endpoint, &bus_cfg)) + if (v4l2_fwnode_endpoint_parse(of_fwnode_handle(endpoint), &bus_cfg)) goto done; pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL); diff --git a/drivers/media/pci/bt8xx/dst_ca.c b/drivers/media/pci/bt8xx/dst_ca.c index 04d06c564602..90f4263452d3 100644 --- a/drivers/media/pci/bt8xx/dst_ca.c +++ b/drivers/media/pci/bt8xx/dst_ca.c @@ -637,6 +637,7 @@ static long dst_ca_ioctl(struct file *file, unsigned int cmd, unsigned long ioct goto free_mem_and_exit; } dprintk(verbose, DST_CA_INFO, 1, " -->CA_SET_PID Success !"); + break; default: result = -EOPNOTSUPP; } diff --git a/drivers/media/pci/cobalt/cobalt-driver.c b/drivers/media/pci/cobalt/cobalt-driver.c index d5c911c09e2b..f8e173f3e9e2 100644 --- a/drivers/media/pci/cobalt/cobalt-driver.c +++ b/drivers/media/pci/cobalt/cobalt-driver.c @@ -205,6 +205,8 @@ void cobalt_pcie_status_show(struct cobalt *cobalt) offset = pci_find_capability(pci_dev, PCI_CAP_ID_EXP); bus_offset = pci_find_capability(pci_bus_dev, PCI_CAP_ID_EXP); + if (!offset || !bus_offset) + return; /* Device */ pci_read_config_dword(pci_dev, offset + PCI_EXP_DEVCAP, &capa); diff --git a/drivers/media/pci/cx18/cx18-alsa-pcm.c b/drivers/media/pci/cx18/cx18-alsa-pcm.c index 205a98da877c..f68ee57a9ae2 100644 --- a/drivers/media/pci/cx18/cx18-alsa-pcm.c +++ b/drivers/media/pci/cx18/cx18-alsa-pcm.c @@ -257,14 +257,16 @@ static int snd_cx18_pcm_hw_free(struct snd_pcm_substream *substream) { struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream); unsigned long flags; + unsigned char *dma_area = NULL; spin_lock_irqsave(&cxsc->slock, flags); if (substream->runtime->dma_area) { dprintk("freeing pcm capture region\n"); - vfree(substream->runtime->dma_area); + dma_area = substream->runtime->dma_area; substream->runtime->dma_area = NULL; } spin_unlock_irqrestore(&cxsc->slock, flags); + vfree(dma_area); return 0; } diff --git a/drivers/media/pci/cx18/cx18-dvb.c b/drivers/media/pci/cx18/cx18-dvb.c index d130d65828b0..53f4d6bf81fb 100644 --- a/drivers/media/pci/cx18/cx18-dvb.c +++ b/drivers/media/pci/cx18/cx18-dvb.c @@ -151,7 +151,7 @@ static int yuan_mpc718_mt352_reqfw(struct cx18_stream *stream, } if (ret) { - CX18_ERR("The MPC718 board variant with the MT352 DVB-Tdemodualtor will not work without it\n"); + CX18_ERR("The MPC718 board variant with the MT352 DVB-T demodulator will not work without it\n"); CX18_ERR("Run 'linux/Documentation/dvb/get_dvb_firmware mpc718' if you need the firmware\n"); } return ret; diff --git a/drivers/media/pci/cx23885/cx23885-cards.c b/drivers/media/pci/cx23885/cx23885-cards.c index 9e39aea85df6..c48fa8e25a70 100644 --- a/drivers/media/pci/cx23885/cx23885-cards.c +++ b/drivers/media/pci/cx23885/cx23885-cards.c @@ -2081,7 +2081,7 @@ void cx23885_card_setup(struct cx23885_dev *dev) ts2->gen_ctrl_val = 0xc; /* Serial bus + punctured clock */ ts2->ts_clk_en_val = 0x1; /* Enable TS_CLK */ ts2->src_sel_val = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO; - /* break omitted intentionally */ + /* fall-through */ case CX23885_BOARD_DVICO_FUSIONHDTV_5_EXP: ts1->gen_ctrl_val = 0xc; /* Serial bus + punctured clock */ ts1->ts_clk_en_val = 0x1; /* Enable TS_CLK */ @@ -2238,6 +2238,7 @@ void cx23885_card_setup(struct cx23885_dev *dev) /* Currently only enabled for the integrated IR controller */ if (!enable_885_ir) break; + /* fall-through */ case CX23885_BOARD_HAUPPAUGE_HVR1250: case CX23885_BOARD_HAUPPAUGE_HVR1800: case CX23885_BOARD_HAUPPAUGE_IMPACTVCBE: diff --git a/drivers/media/pci/cx88/cx88-cards.c b/drivers/media/pci/cx88/cx88-cards.c index 73cc7a67a8bc..6df21b29ea17 100644 --- a/drivers/media/pci/cx88/cx88-cards.c +++ b/drivers/media/pci/cx88/cx88-cards.c @@ -3681,7 +3681,14 @@ struct cx88_core *cx88_core_create(struct pci_dev *pci, int nr) core->nr = nr; sprintf(core->name, "cx88[%d]", core->nr); - core->tvnorm = V4L2_STD_NTSC_M; + /* + * Note: Setting initial standard here would cause first call to + * cx88_set_tvnorm() to return without programming any registers. Leave + * it blank for at this point and it will get set later in + * cx8800_initdev() + */ + core->tvnorm = 0; + core->width = 320; core->height = 240; core->field = V4L2_FIELD_INTERLACED; diff --git a/drivers/media/pci/cx88/cx88-video.c b/drivers/media/pci/cx88/cx88-video.c index c7d4e87ccb64..7d25ecd4404b 100644 --- a/drivers/media/pci/cx88/cx88-video.c +++ b/drivers/media/pci/cx88/cx88-video.c @@ -1420,7 +1420,7 @@ static int cx8800_initdev(struct pci_dev *pci_dev, request_module("rtc-isl1208"); core->i2c_rtc = i2c_new_device(&core->i2c_adap, &rtc_info); } - /* break intentionally omitted */ + /* fall-through */ case CX88_BOARD_DVICO_FUSIONHDTV_5_PCI_NANO: request_module("ir-kbd-i2c"); } @@ -1435,7 +1435,7 @@ static int cx8800_initdev(struct pci_dev *pci_dev, /* initial device configuration */ mutex_lock(&core->lock); - cx88_set_tvnorm(core, core->tvnorm); + cx88_set_tvnorm(core, V4L2_STD_NTSC_M); v4l2_ctrl_handler_setup(&core->video_hdl); v4l2_ctrl_handler_setup(&core->audio_hdl); cx88_video_mux(core, 0); diff --git a/drivers/media/pci/ddbridge/ddbridge-core.c b/drivers/media/pci/ddbridge/ddbridge-core.c index 340cff02dee2..09b5338f4f8b 100644 --- a/drivers/media/pci/ddbridge/ddbridge-core.c +++ b/drivers/media/pci/ddbridge/ddbridge-core.c @@ -789,9 +789,10 @@ static void dvb_input_detach(struct ddb_input *input) dvb_frontend_detach(input->fe); input->fe = NULL; } + /* fall-through */ case 4: dvb_net_release(&input->dvbnet); - + /* fall-through */ case 3: dvbdemux->dmx.close(&dvbdemux->dmx); dvbdemux->dmx.remove_frontend(&dvbdemux->dmx, @@ -799,10 +800,10 @@ static void dvb_input_detach(struct ddb_input *input) dvbdemux->dmx.remove_frontend(&dvbdemux->dmx, &input->mem_frontend); dvb_dmxdev_release(&input->dmxdev); - + /* fall-through */ case 2: dvb_dmx_release(&input->demux); - + /* fall-through */ case 1: dvb_unregister_adapter(adap); } diff --git a/drivers/media/pci/ivtv/ivtv-alsa-pcm.c b/drivers/media/pci/ivtv/ivtv-alsa-pcm.c index 807ead20d212..417d03da01f0 100644 --- a/drivers/media/pci/ivtv/ivtv-alsa-pcm.c +++ b/drivers/media/pci/ivtv/ivtv-alsa-pcm.c @@ -262,14 +262,16 @@ static int snd_ivtv_pcm_hw_free(struct snd_pcm_substream *substream) { struct snd_ivtv_card *itvsc = snd_pcm_substream_chip(substream); unsigned long flags; + unsigned char *dma_area = NULL; spin_lock_irqsave(&itvsc->slock, flags); if (substream->runtime->dma_area) { dprintk("freeing pcm capture region\n"); - vfree(substream->runtime->dma_area); + dma_area = substream->runtime->dma_area; substream->runtime->dma_area = NULL; } spin_unlock_irqrestore(&itvsc->slock, flags); + vfree(dma_area); return 0; } diff --git a/drivers/media/pci/saa7134/saa7134-cards.c b/drivers/media/pci/saa7134/saa7134-cards.c index f79380faf499..9965d3531c80 100644 --- a/drivers/media/pci/saa7134/saa7134-cards.c +++ b/drivers/media/pci/saa7134/saa7134-cards.c @@ -7806,7 +7806,7 @@ int saa7134_board_init2(struct saa7134_dev *dev) dev->name, saa7134_boards[dev->board].name); break; } - /* break intentionally omitted */ + /* fall-through */ case SAA7134_BOARD_VIDEOMATE_DVBT_300: case SAA7134_BOARD_ASUS_EUROPA2_HYBRID: case SAA7134_BOARD_ASUS_EUROPA_HYBRID: @@ -7864,7 +7864,7 @@ int saa7134_board_init2(struct saa7134_dev *dev) break; case SAA7134_BOARD_HAUPPAUGE_HVR1110: hauppauge_eeprom(dev, dev->eedata+0x80); - /* break intentionally omitted */ + /* fall-through */ case SAA7134_BOARD_PINNACLE_PCTV_310i: case SAA7134_BOARD_KWORLD_DVBT_210: case SAA7134_BOARD_TEVION_DVBT_220RF: diff --git a/drivers/media/pci/saa7164/saa7164-bus.c b/drivers/media/pci/saa7164/saa7164-bus.c index b2ff82fa7116..ecfeac5cdbed 100644 --- a/drivers/media/pci/saa7164/saa7164-bus.c +++ b/drivers/media/pci/saa7164/saa7164-bus.c @@ -389,11 +389,11 @@ int saa7164_bus_get(struct saa7164_dev *dev, struct tmComResInfo* msg, msg_tmp.size = le16_to_cpu((__force __le16)msg_tmp.size); msg_tmp.command = le32_to_cpu((__force __le32)msg_tmp.command); msg_tmp.controlselector = le16_to_cpu((__force __le16)msg_tmp.controlselector); + memcpy(msg, &msg_tmp, sizeof(*msg)); /* No need to update the read positions, because this was a peek */ /* If the caller specifically want to peek, return */ if (peekonly) { - memcpy(msg, &msg_tmp, sizeof(*msg)); goto peekout; } @@ -438,21 +438,15 @@ int saa7164_bus_get(struct saa7164_dev *dev, struct tmComResInfo* msg, space_rem = bus->m_dwSizeGetRing - curr_grp; if (space_rem < sizeof(*msg)) { - /* msg wraps around the ring */ - memcpy_fromio(msg, bus->m_pdwGetRing + curr_grp, space_rem); - memcpy_fromio((u8 *)msg + space_rem, bus->m_pdwGetRing, - sizeof(*msg) - space_rem); if (buf) memcpy_fromio(buf, bus->m_pdwGetRing + sizeof(*msg) - space_rem, buf_size); } else if (space_rem == sizeof(*msg)) { - memcpy_fromio(msg, bus->m_pdwGetRing + curr_grp, sizeof(*msg)); if (buf) memcpy_fromio(buf, bus->m_pdwGetRing, buf_size); } else { /* Additional data wraps around the ring */ - memcpy_fromio(msg, bus->m_pdwGetRing + curr_grp, sizeof(*msg)); if (buf) { memcpy_fromio(buf, bus->m_pdwGetRing + curr_grp + sizeof(*msg), space_rem - sizeof(*msg)); @@ -465,15 +459,10 @@ int saa7164_bus_get(struct saa7164_dev *dev, struct tmComResInfo* msg, } else { /* No wrapping */ - memcpy_fromio(msg, bus->m_pdwGetRing + curr_grp, sizeof(*msg)); if (buf) memcpy_fromio(buf, bus->m_pdwGetRing + curr_grp + sizeof(*msg), buf_size); } - /* Convert from little endian to CPU */ - msg->size = le16_to_cpu((__force __le16)msg->size); - msg->command = le32_to_cpu((__force __le32)msg->command); - msg->controlselector = le16_to_cpu((__force __le16)msg->controlselector); /* Update the read positions, adjusting the ring */ saa7164_writel(bus->m_dwGetReadPos, new_grp); diff --git a/drivers/media/pci/saa7164/saa7164-cmd.c b/drivers/media/pci/saa7164/saa7164-cmd.c index 175015ca79f2..dfebd77ada59 100644 --- a/drivers/media/pci/saa7164/saa7164-cmd.c +++ b/drivers/media/pci/saa7164/saa7164-cmd.c @@ -506,6 +506,8 @@ int saa7164_cmd_send(struct saa7164_dev *dev, u8 id, enum tmComResCmd command, dprintk(DBGLVL_CMD, "%s() UNKNOWN OR INVALID CONTROL\n", __func__); + ret = SAA_ERR_NOT_SUPPORTED; + break; default: dprintk(DBGLVL_CMD, "%s() UNKNOWN\n", __func__); ret = SAA_ERR_NOT_SUPPORTED; diff --git a/drivers/media/pci/solo6x10/solo6x10-core.c b/drivers/media/pci/solo6x10/solo6x10-core.c index f50d07229236..ca0873e47bea 100644 --- a/drivers/media/pci/solo6x10/solo6x10-core.c +++ b/drivers/media/pci/solo6x10/solo6x10-core.c @@ -511,6 +511,7 @@ static int solo_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) default: dev_warn(&pdev->dev, "Invalid chip_id 0x%02x, assuming 4 ch\n", chip_id); + /* fall through */ case 5: solo_dev->nr_chans = 4; solo_dev->nr_ext = 1; diff --git a/drivers/media/pci/solo6x10/solo6x10-i2c.c b/drivers/media/pci/solo6x10/solo6x10-i2c.c index e83bb79f9349..89f2f2a493c2 100644 --- a/drivers/media/pci/solo6x10/solo6x10-i2c.c +++ b/drivers/media/pci/solo6x10/solo6x10-i2c.c @@ -192,6 +192,7 @@ int solo_i2c_isr(struct solo_dev *solo_dev) } solo_dev->i2c_state = IIC_STATE_WRITE; + /* fall through */ case IIC_STATE_WRITE: ret = solo_i2c_handle_write(solo_dev); break; diff --git a/drivers/media/pci/ttpci/av7110.c b/drivers/media/pci/ttpci/av7110.c index df9395c87178..f2905bd80366 100644 --- a/drivers/media/pci/ttpci/av7110.c +++ b/drivers/media/pci/ttpci/av7110.c @@ -336,6 +336,7 @@ static int DvbDmxFilterCallback(u8 *buffer1, size_t buffer1_len, av7110_p2t_write(buffer1, buffer1_len, dvbdmxfilter->feed->pid, &av7110->p2t_filter[dvbdmxfilter->index]); + return 0; default: return 0; } @@ -451,8 +452,12 @@ static void debiirq(unsigned long cookie) case DATA_CI_PUT: dprintk(4, "debi DATA_CI_PUT\n"); + xfer = TX_BUFF; + break; case DATA_MPEG_PLAY: dprintk(4, "debi DATA_MPEG_PLAY\n"); + xfer = TX_BUFF; + break; case DATA_BMP_LOAD: dprintk(4, "debi DATA_BMP_LOAD\n"); xfer = TX_BUFF; diff --git a/drivers/media/pci/zoran/zoran_driver.c b/drivers/media/pci/zoran/zoran_driver.c index 180f3d7af3e1..a11cb501c550 100644 --- a/drivers/media/pci/zoran/zoran_driver.c +++ b/drivers/media/pci/zoran/zoran_driver.c @@ -534,6 +534,7 @@ static int zoran_v4l_queue_frame(struct zoran_fh *fh, int num) KERN_WARNING "%s: %s - queueing buffer %d in state DONE!?\n", ZR_DEVNAME(zr), __func__, num); + /* fall through */ case BUZ_STATE_USER: /* since there is at least one unused buffer there's room for at least * one more pend[] entry */ @@ -693,6 +694,7 @@ static int zoran_jpg_queue_frame(struct zoran_fh *fh, int num, KERN_WARNING "%s: %s - queing frame in BUZ_STATE_DONE state!?\n", ZR_DEVNAME(zr), __func__); + /* fall through */ case BUZ_STATE_USER: /* since there is at least one unused buffer there's room for at *least one more pend[] entry */ diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig index 041cb80a26b1..3c1eb4f63936 100644 --- a/drivers/media/platform/Kconfig +++ b/drivers/media/platform/Kconfig @@ -74,6 +74,13 @@ config VIDEO_M32R_AR_M64278 To compile this driver as a module, choose M here: the module will be called arv. +config VIDEO_MUX + tristate "Video Multiplexer" + depends on OF && VIDEO_V4L2_SUBDEV_API && MEDIA_CONTROLLER + select REGMAP + help + This driver provides support for N:1 video bus multiplexers. + config VIDEO_OMAP3 tristate "OMAP 3 Camera support" depends on VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API && ARCH_OMAP3 @@ -82,6 +89,7 @@ config VIDEO_OMAP3 select ARM_DMA_USE_IOMMU select VIDEOBUF2_DMA_CONTIG select MFD_SYSCON + select V4L2_FWNODE ---help--- Driver for an OMAP 3 camera controller. @@ -97,6 +105,7 @@ config VIDEO_PXA27x depends on PXA27x || COMPILE_TEST select VIDEOBUF2_DMA_SG select SG_SPLIT + select V4L2_FWNODE ---help--- This is a v4l2 driver for the PXA27x Quick Capture Interface @@ -114,6 +123,19 @@ config VIDEO_S3C_CAMIF To compile this driver as a module, choose M here: the module will be called s3c-camif. +config VIDEO_STM32_DCMI + tristate "STM32 Digital Camera Memory Interface (DCMI) support" + depends on VIDEO_V4L2 && OF && HAS_DMA + depends on ARCH_STM32 || COMPILE_TEST + select VIDEOBUF2_DMA_CONTIG + select V4L2_FWNODE + ---help--- + This module makes the STM32 Digital Camera Memory Interface (DCMI) + available as a v4l2 device. + + To compile this driver as a module, choose M here: the module + will be called stm32-dcmi. + source "drivers/media/platform/soc_camera/Kconfig" source "drivers/media/platform/exynos4-is/Kconfig" source "drivers/media/platform/am437x/Kconfig" @@ -127,6 +149,7 @@ config VIDEO_TI_CAL depends on SOC_DRA7XX || COMPILE_TEST depends on HAS_DMA select VIDEOBUF2_DMA_CONTIG + select V4L2_FWNODE default n ---help--- Support for the TI CAL (Camera Adaptation Layer) block @@ -521,4 +544,41 @@ config VIDEO_STI_HDMI_CEC CEC bus is present in the HDMI connector and enables communication between compatible devices. +config VIDEO_STM32_HDMI_CEC + tristate "STMicroelectronics STM32 HDMI CEC driver" + depends on ARCH_STM32 || COMPILE_TEST + select REGMAP + select REGMAP_MMIO + select CEC_CORE + ---help--- + This is a driver for STM32 interface. It uses the + generic CEC framework interface. + CEC bus is present in the HDMI connector and enables communication + between compatible devices. + endif #CEC_PLATFORM_DRIVERS + +menuconfig SDR_PLATFORM_DRIVERS + bool "SDR platform devices" + depends on MEDIA_SDR_SUPPORT + default n + ---help--- + Say Y here to enable support for platform-specific SDR Drivers. + +if SDR_PLATFORM_DRIVERS + +config VIDEO_RCAR_DRIF + tristate "Renesas Digitial Radio Interface (DRIF)" + depends on VIDEO_V4L2 && HAS_DMA + depends on ARCH_RENESAS || COMPILE_TEST + select VIDEOBUF2_VMALLOC + ---help--- + Say Y if you want to enable R-Car Gen3 DRIF support. DRIF is Digital + Radio Interface that interfaces with an RF front end chip. It is a + receiver of digital data which uses DMA to transfer received data to + a configured location for an application to use. + + To compile this driver as a module, choose M here; the module + will be called rcar_drif. + +endif # SDR_PLATFORM_DRIVERS diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile index 63303d63c64c..ba7981d6f3c5 100644 --- a/drivers/media/platform/Makefile +++ b/drivers/media/platform/Makefile @@ -28,6 +28,8 @@ obj-$(CONFIG_VIDEO_SH_VEU) += sh_veu.o obj-$(CONFIG_VIDEO_MEM2MEM_DEINTERLACE) += m2m-deinterlace.o +obj-$(CONFIG_VIDEO_MUX) += video-mux.o + obj-$(CONFIG_VIDEO_S3C_CAMIF) += s3c-camif/ obj-$(CONFIG_VIDEO_SAMSUNG_EXYNOS4_IS) += exynos4-is/ obj-$(CONFIG_VIDEO_SAMSUNG_S5P_JPEG) += s5p-jpeg/ @@ -44,14 +46,17 @@ obj-$(CONFIG_VIDEO_STI_HDMI_CEC) += sti/cec/ obj-$(CONFIG_VIDEO_STI_DELTA) += sti/delta/ -obj-$(CONFIG_BLACKFIN) += blackfin/ +obj-y += stm32/ + +obj-y += blackfin/ -obj-$(CONFIG_ARCH_DAVINCI) += davinci/ +obj-y += davinci/ obj-$(CONFIG_VIDEO_SH_VOU) += sh_vou.o obj-$(CONFIG_SOC_CAMERA) += soc_camera/ +obj-$(CONFIG_VIDEO_RCAR_DRIF) += rcar_drif.o obj-$(CONFIG_VIDEO_RENESAS_FCP) += rcar-fcp.o obj-$(CONFIG_VIDEO_RENESAS_FDP1) += rcar_fdp1.o obj-$(CONFIG_VIDEO_RENESAS_JPU) += rcar_jpu.o @@ -68,6 +73,8 @@ obj-$(CONFIG_VIDEO_RCAR_VIN) += rcar-vin/ obj-$(CONFIG_VIDEO_ATMEL_ISC) += atmel/ obj-$(CONFIG_VIDEO_ATMEL_ISI) += atmel/ +obj-$(CONFIG_VIDEO_STM32_DCMI) += stm32/ + ccflags-y += -I$(srctree)/drivers/media/i2c obj-$(CONFIG_VIDEO_MEDIATEK_VPU) += mtk-vpu/ diff --git a/drivers/media/platform/am437x/Kconfig b/drivers/media/platform/am437x/Kconfig index 42d9c186710a..160e77e9a0fb 100644 --- a/drivers/media/platform/am437x/Kconfig +++ b/drivers/media/platform/am437x/Kconfig @@ -3,6 +3,7 @@ config VIDEO_AM437X_VPFE depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API && HAS_DMA depends on SOC_AM43XX || COMPILE_TEST select VIDEOBUF2_DMA_CONTIG + select V4L2_FWNODE help Support for AM437x Video Processing Front End based Video Capture Driver. diff --git a/drivers/media/platform/am437x/am437x-vpfe.c b/drivers/media/platform/am437x/am437x-vpfe.c index 05489a401c5c..466aba8b0e00 100644 --- a/drivers/media/platform/am437x/am437x-vpfe.c +++ b/drivers/media/platform/am437x/am437x-vpfe.c @@ -26,6 +26,7 @@ #include <linux/interrupt.h> #include <linux/io.h> #include <linux/module.h> +#include <linux/of_graph.h> #include <linux/pinctrl/consumer.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> @@ -36,7 +37,7 @@ #include <media/v4l2-common.h> #include <media/v4l2-ctrls.h> #include <media/v4l2-event.h> -#include <media/v4l2-of.h> +#include <media/v4l2-fwnode.h> #include "am437x-vpfe.h" @@ -2303,7 +2304,8 @@ vpfe_async_bound(struct v4l2_async_notifier *notifier, vpfe_dbg(1, vpfe, "vpfe_async_bound\n"); for (i = 0; i < ARRAY_SIZE(vpfe->cfg->asd); i++) { - if (vpfe->cfg->asd[i]->match.of.node == asd[i].match.of.node) { + if (vpfe->cfg->asd[i]->match.fwnode.fwnode == + asd[i].match.fwnode.fwnode) { sdinfo = &vpfe->cfg->sub_devs[i]; vpfe->sd[i] = subdev; vpfe->sd[i]->grp_id = sdinfo->grp_id; @@ -2419,7 +2421,7 @@ static struct vpfe_config * vpfe_get_pdata(struct platform_device *pdev) { struct device_node *endpoint = NULL; - struct v4l2_of_endpoint bus_cfg; + struct v4l2_fwnode_endpoint bus_cfg; struct vpfe_subdev_info *sdinfo; struct vpfe_config *pdata; unsigned int flags; @@ -2463,7 +2465,8 @@ vpfe_get_pdata(struct platform_device *pdev) sdinfo->vpfe_param.if_type = VPFE_RAW_BAYER; } - err = v4l2_of_parse_endpoint(endpoint, &bus_cfg); + err = v4l2_fwnode_endpoint_parse(of_fwnode_handle(endpoint), + &bus_cfg); if (err) { dev_err(&pdev->dev, "Could not parse the endpoint\n"); goto done; @@ -2501,8 +2504,8 @@ vpfe_get_pdata(struct platform_device *pdev) goto done; } - pdata->asd[i]->match_type = V4L2_ASYNC_MATCH_OF; - pdata->asd[i]->match.of.node = rem; + pdata->asd[i]->match_type = V4L2_ASYNC_MATCH_FWNODE; + pdata->asd[i]->match.fwnode.fwnode = of_fwnode_handle(rem); of_node_put(rem); } diff --git a/drivers/media/platform/atmel/Kconfig b/drivers/media/platform/atmel/Kconfig index 9bd0f19b127f..55de751e5f51 100644 --- a/drivers/media/platform/atmel/Kconfig +++ b/drivers/media/platform/atmel/Kconfig @@ -4,6 +4,7 @@ config VIDEO_ATMEL_ISC depends on ARCH_AT91 || COMPILE_TEST select VIDEOBUF2_DMA_CONTIG select REGMAP_MMIO + select V4L2_FWNODE help This module makes the ATMEL Image Sensor Controller available as a v4l2 device. @@ -13,6 +14,7 @@ config VIDEO_ATMEL_ISI depends on VIDEO_V4L2 && OF && HAS_DMA depends on ARCH_AT91 || COMPILE_TEST select VIDEOBUF2_DMA_CONTIG + select V4L2_FWNODE ---help--- This module makes the ATMEL Image Sensor Interface available as a v4l2 device. diff --git a/drivers/media/platform/atmel/atmel-isc.c b/drivers/media/platform/atmel/atmel-isc.c index c4b2115559a5..d6534252cdcd 100644 --- a/drivers/media/platform/atmel/atmel-isc.c +++ b/drivers/media/platform/atmel/atmel-isc.c @@ -32,6 +32,7 @@ #include <linux/math64.h> #include <linux/module.h> #include <linux/of.h> +#include <linux/of_graph.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> #include <linux/regmap.h> @@ -42,7 +43,7 @@ #include <media/v4l2-event.h> #include <media/v4l2-image-sizes.h> #include <media/v4l2-ioctl.h> -#include <media/v4l2-of.h> +#include <media/v4l2-fwnode.h> #include <media/v4l2-subdev.h> #include <media/videobuf2-dma-contig.h> @@ -239,13 +240,11 @@ static struct isc_format isc_formats[] = { { V4L2_PIX_FMT_YUV420, 0x0, 12, ISC_PFE_CFG0_BPS_EIGHT, ISC_BAY_CFG_BGBG, ISC_RLP_CFG_MODE_YYCC, - ISC_DCFG_IMODE_YC420P | ISC_DCFG_YMBSIZE_BEATS8 | - ISC_DCFG_CMBSIZE_BEATS8, ISC_DCTRL_DVIEW_PLANAR, 0x7fb, + ISC_DCFG_IMODE_YC420P, ISC_DCTRL_DVIEW_PLANAR, 0x7fb, false, false }, { V4L2_PIX_FMT_YUV422P, 0x0, 16, ISC_PFE_CFG0_BPS_EIGHT, ISC_BAY_CFG_BGBG, ISC_RLP_CFG_MODE_YYCC, - ISC_DCFG_IMODE_YC422P | ISC_DCFG_YMBSIZE_BEATS8 | - ISC_DCFG_CMBSIZE_BEATS8, ISC_DCTRL_DVIEW_PLANAR, 0x3fb, + ISC_DCFG_IMODE_YC422P, ISC_DCTRL_DVIEW_PLANAR, 0x3fb, false, false }, { V4L2_PIX_FMT_RGB565, MEDIA_BUS_FMT_RGB565_2X8_LE, 16, ISC_PFE_CFG0_BPS_EIGHT, ISC_BAY_CFG_BGBG, ISC_RLP_CFG_MODE_RGB565, @@ -700,8 +699,10 @@ static void isc_set_histogram(struct isc_device *isc) } static inline void isc_get_param(const struct isc_format *fmt, - u32 *rlp_mode, u32 *dcfg_imode) + u32 *rlp_mode, u32 *dcfg) { + *dcfg = ISC_DCFG_YMBSIZE_BEATS8; + switch (fmt->fourcc) { case V4L2_PIX_FMT_SBGGR10: case V4L2_PIX_FMT_SGBRG10: @@ -712,11 +713,11 @@ static inline void isc_get_param(const struct isc_format *fmt, case V4L2_PIX_FMT_SGRBG12: case V4L2_PIX_FMT_SRGGB12: *rlp_mode = fmt->reg_rlp_mode; - *dcfg_imode = fmt->reg_dcfg_imode; + *dcfg |= fmt->reg_dcfg_imode; break; default: *rlp_mode = ISC_RLP_CFG_MODE_DAT8; - *dcfg_imode = ISC_DCFG_IMODE_PACKED8; + *dcfg |= ISC_DCFG_IMODE_PACKED8; break; } } @@ -726,18 +727,19 @@ static int isc_configure(struct isc_device *isc) struct regmap *regmap = isc->regmap; const struct isc_format *current_fmt = isc->current_fmt; struct isc_subdev_entity *subdev = isc->current_subdev; - u32 pfe_cfg0, rlp_mode, dcfg_imode, mask, pipeline; + u32 pfe_cfg0, rlp_mode, dcfg, mask, pipeline; if (sensor_is_preferred(current_fmt)) { pfe_cfg0 = current_fmt->reg_bps; pipeline = 0x0; - isc_get_param(current_fmt, &rlp_mode, &dcfg_imode); + isc_get_param(current_fmt, &rlp_mode, &dcfg); isc->ctrls.hist_stat = HIST_INIT; } else { pfe_cfg0 = isc->raw_fmt->reg_bps; pipeline = current_fmt->pipeline; rlp_mode = current_fmt->reg_rlp_mode; - dcfg_imode = current_fmt->reg_dcfg_imode; + dcfg = current_fmt->reg_dcfg_imode | ISC_DCFG_YMBSIZE_BEATS8 | + ISC_DCFG_CMBSIZE_BEATS8; } pfe_cfg0 |= subdev->pfe_cfg0 | ISC_PFE_CFG0_MODE_PROGRESSIVE; @@ -750,7 +752,7 @@ static int isc_configure(struct isc_device *isc) regmap_update_bits(regmap, ISC_RLP_CFG, ISC_RLP_CFG_MODE_MASK, rlp_mode); - regmap_update_bits(regmap, ISC_DCFG, ISC_DCFG_IMODE_MASK, dcfg_imode); + regmap_write(regmap, ISC_DCFG, dcfg); /* Set the pipeline */ isc_set_pipeline(isc, pipeline); @@ -1684,7 +1686,7 @@ static int isc_parse_dt(struct device *dev, struct isc_device *isc) { struct device_node *np = dev->of_node; struct device_node *epn = NULL, *rem; - struct v4l2_of_endpoint v4l2_epn; + struct v4l2_fwnode_endpoint v4l2_epn; struct isc_subdev_entity *subdev_entity; unsigned int flags; int ret; @@ -1703,7 +1705,8 @@ static int isc_parse_dt(struct device *dev, struct isc_device *isc) continue; } - ret = v4l2_of_parse_endpoint(epn, &v4l2_epn); + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(epn), + &v4l2_epn); if (ret) { of_node_put(rem); ret = -EINVAL; @@ -1738,8 +1741,9 @@ static int isc_parse_dt(struct device *dev, struct isc_device *isc) if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING) subdev_entity->pfe_cfg0 |= ISC_PFE_CFG0_PPOL_LOW; - subdev_entity->asd->match_type = V4L2_ASYNC_MATCH_OF; - subdev_entity->asd->match.of.node = rem; + subdev_entity->asd->match_type = V4L2_ASYNC_MATCH_FWNODE; + subdev_entity->asd->match.fwnode.fwnode = + of_fwnode_handle(rem); list_add_tail(&subdev_entity->list, &isc->subdev_entities); } diff --git a/drivers/media/platform/atmel/atmel-isi.c b/drivers/media/platform/atmel/atmel-isi.c index e4867f84514c..891fa2505efa 100644 --- a/drivers/media/platform/atmel/atmel-isi.c +++ b/drivers/media/platform/atmel/atmel-isi.c @@ -19,6 +19,7 @@ #include <linux/interrupt.h> #include <linux/kernel.h> #include <linux/module.h> +#include <linux/of_graph.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> #include <linux/slab.h> @@ -30,14 +31,14 @@ #include <media/v4l2-dev.h> #include <media/v4l2-ioctl.h> #include <media/v4l2-event.h> -#include <media/v4l2-of.h> +#include <media/v4l2-fwnode.h> #include <media/videobuf2-dma-contig.h> #include <media/v4l2-image-sizes.h> #include "atmel-isi.h" -#define MAX_SUPPORT_WIDTH 2048 -#define MAX_SUPPORT_HEIGHT 2048 +#define MAX_SUPPORT_WIDTH 2048U +#define MAX_SUPPORT_HEIGHT 2048U #define MIN_FRAME_RATE 15 #define FRAME_INTERVAL_MILLI_SEC (1000 / MIN_FRAME_RATE) @@ -424,6 +425,8 @@ static int start_streaming(struct vb2_queue *vq, unsigned int count) struct frame_buffer *buf, *node; int ret; + pm_runtime_get_sync(isi->dev); + /* Enable stream on the sub device */ ret = v4l2_subdev_call(isi->entity.subdev, video, s_stream, 1); if (ret && ret != -ENOIOCTLCMD) { @@ -431,8 +434,6 @@ static int start_streaming(struct vb2_queue *vq, unsigned int count) goto err_start_stream; } - pm_runtime_get_sync(isi->dev); - /* Reset ISI */ ret = atmel_isi_wait_status(isi, WAIT_ISI_RESET); if (ret < 0) { @@ -455,10 +456,11 @@ static int start_streaming(struct vb2_queue *vq, unsigned int count) return 0; err_reset: - pm_runtime_put(isi->dev); v4l2_subdev_call(isi->entity.subdev, video, s_stream, 0); err_start_stream: + pm_runtime_put(isi->dev); + spin_lock_irq(&isi->irqlock); isi->active = NULL; /* Release all active buffers */ @@ -566,20 +568,15 @@ static int isi_try_fmt(struct atmel_isi *isi, struct v4l2_format *f, }; int ret; - if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) - return -EINVAL; - isi_fmt = find_format_by_fourcc(isi, pixfmt->pixelformat); if (!isi_fmt) { isi_fmt = isi->user_formats[isi->num_user_formats - 1]; pixfmt->pixelformat = isi_fmt->fourcc; } - /* Limit to Atmel ISC hardware capabilities */ - if (pixfmt->width > MAX_SUPPORT_WIDTH) - pixfmt->width = MAX_SUPPORT_WIDTH; - if (pixfmt->height > MAX_SUPPORT_HEIGHT) - pixfmt->height = MAX_SUPPORT_HEIGHT; + /* Limit to Atmel ISI hardware capabilities */ + pixfmt->width = clamp(pixfmt->width, 0U, MAX_SUPPORT_WIDTH); + pixfmt->height = clamp(pixfmt->height, 0U, MAX_SUPPORT_HEIGHT); v4l2_fill_mbus_format(&format.format, pixfmt, isi_fmt->mbus_code); ret = v4l2_subdev_call(isi->entity.subdev, pad, set_fmt, @@ -801,7 +798,7 @@ static int atmel_isi_parse_dt(struct atmel_isi *isi, struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; - struct v4l2_of_endpoint ep; + struct v4l2_fwnode_endpoint ep; int err; /* Default settings for ISI */ @@ -814,7 +811,7 @@ static int atmel_isi_parse_dt(struct atmel_isi *isi, return -EINVAL; } - err = v4l2_of_parse_endpoint(np, &ep); + err = v4l2_fwnode_endpoint_parse(of_fwnode_handle(np), &ep); of_node_put(np); if (err) { dev_err(&pdev->dev, "Could not parse the endpoint\n"); @@ -1058,7 +1055,7 @@ static int isi_graph_notify_complete(struct v4l2_async_notifier *notifier) struct atmel_isi *isi = notifier_to_isi(notifier); int ret; - isi->vdev->ctrl_handler = isi->entity.subdev->ctrl_handler; + isi->vdev->ctrl_handler = isi->entity.subdev->ctrl_handler; ret = isi_formats_init(isi); if (ret) { dev_err(isi->dev, "No supported mediabus format found\n"); @@ -1126,8 +1123,8 @@ static int isi_graph_parse(struct atmel_isi *isi, struct device_node *node) /* Remote node to connect */ isi->entity.node = remote; - isi->entity.asd.match_type = V4L2_ASYNC_MATCH_OF; - isi->entity.asd.match.of.node = remote; + isi->entity.asd.match_type = V4L2_ASYNC_MATCH_FWNODE; + isi->entity.asd.match.fwnode.fwnode = of_fwnode_handle(remote); return 0; } } diff --git a/drivers/media/platform/coda/coda-bit.c b/drivers/media/platform/coda/coda-bit.c index 403214e00e95..2ec41375a896 100644 --- a/drivers/media/platform/coda/coda-bit.c +++ b/drivers/media/platform/coda/coda-bit.c @@ -1247,12 +1247,18 @@ static int coda_prepare_encode(struct coda_ctx *ctx) dst_buf->sequence = ctx->osequence; ctx->osequence++; + force_ipicture = ctx->params.force_ipicture; + if (force_ipicture) + ctx->params.force_ipicture = false; + else if ((src_buf->sequence % ctx->params.gop_size) == 0) + force_ipicture = 1; + /* * Workaround coda firmware BUG that only marks the first * frame as IDR. This is a problem for some decoders that can't * recover when a frame is lost. */ - if (src_buf->sequence % ctx->params.gop_size) { + if (!force_ipicture) { src_buf->flags |= V4L2_BUF_FLAG_PFRAME; src_buf->flags &= ~V4L2_BUF_FLAG_KEYFRAME; } else { @@ -1264,10 +1270,10 @@ static int coda_prepare_encode(struct coda_ctx *ctx) coda_set_gdi_regs(ctx); /* - * Copy headers at the beginning of the first frame for H.264 only. - * In MPEG4 they are already copied by the coda. + * Copy headers in front of the first frame and forced I frames for + * H.264 only. In MPEG4 they are already copied by the CODA. */ - if (src_buf->sequence == 0) { + if (src_buf->sequence == 0 || force_ipicture) { pic_stream_buffer_addr = vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0) + ctx->vpu_header_size[0] + @@ -1291,8 +1297,7 @@ static int coda_prepare_encode(struct coda_ctx *ctx) pic_stream_buffer_size = q_data_dst->sizeimage; } - if (src_buf->flags & V4L2_BUF_FLAG_KEYFRAME) { - force_ipicture = 1; + if (force_ipicture) { switch (dst_fourcc) { case V4L2_PIX_FMT_H264: quant_param = ctx->params.h264_intra_qp; @@ -1309,7 +1314,6 @@ static int coda_prepare_encode(struct coda_ctx *ctx) break; } } else { - force_ipicture = 0; switch (dst_fourcc) { case V4L2_PIX_FMT_H264: quant_param = ctx->params.h264_inter_qp; @@ -1382,7 +1386,8 @@ static void coda_finish_encode(struct coda_ctx *ctx) wr_ptr = coda_read(dev, CODA_REG_BIT_WR_PTR(ctx->reg_idx)); /* Calculate bytesused field */ - if (dst_buf->sequence == 0) { + if (dst_buf->sequence == 0 || + src_buf->flags & V4L2_BUF_FLAG_KEYFRAME) { vb2_set_plane_payload(&dst_buf->vb2_buf, 0, wr_ptr - start_ptr + ctx->vpu_header_size[0] + ctx->vpu_header_size[1] + diff --git a/drivers/media/platform/coda/coda-common.c b/drivers/media/platform/coda/coda-common.c index d523e990d509..78bd9a4ace0e 100644 --- a/drivers/media/platform/coda/coda-common.c +++ b/drivers/media/platform/coda/coda-common.c @@ -430,10 +430,10 @@ static int coda_g_fmt(struct file *file, void *priv, f->fmt.pix.bytesperline = q_data->bytesperline; f->fmt.pix.sizeimage = q_data->sizeimage; - if (f->fmt.pix.pixelformat == V4L2_PIX_FMT_JPEG) - f->fmt.pix.colorspace = V4L2_COLORSPACE_JPEG; - else - f->fmt.pix.colorspace = ctx->colorspace; + f->fmt.pix.colorspace = ctx->colorspace; + f->fmt.pix.xfer_func = ctx->xfer_func; + f->fmt.pix.ycbcr_enc = ctx->ycbcr_enc; + f->fmt.pix.quantization = ctx->quantization; return 0; } @@ -599,6 +599,9 @@ static int coda_try_fmt_vid_cap(struct file *file, void *priv, } f->fmt.pix.colorspace = ctx->colorspace; + f->fmt.pix.xfer_func = ctx->xfer_func; + f->fmt.pix.ycbcr_enc = ctx->ycbcr_enc; + f->fmt.pix.quantization = ctx->quantization; q_data_src = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); codec = coda_find_codec(ctx->dev, q_data_src->fourcc, @@ -612,7 +615,6 @@ static int coda_try_fmt_vid_cap(struct file *file, void *priv, /* The h.264 decoder only returns complete 16x16 macroblocks */ if (codec && codec->src_fourcc == V4L2_PIX_FMT_H264) { - f->fmt.pix.width = f->fmt.pix.width; f->fmt.pix.height = round_up(f->fmt.pix.height, 16); f->fmt.pix.bytesperline = round_up(f->fmt.pix.width, 16); f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * @@ -635,6 +637,23 @@ static int coda_try_fmt_vid_cap(struct file *file, void *priv, return 0; } +static void coda_set_default_colorspace(struct v4l2_pix_format *fmt) +{ + enum v4l2_colorspace colorspace; + + if (fmt->pixelformat == V4L2_PIX_FMT_JPEG) + colorspace = V4L2_COLORSPACE_JPEG; + else if (fmt->width <= 720 && fmt->height <= 576) + colorspace = V4L2_COLORSPACE_SMPTE170M; + else + colorspace = V4L2_COLORSPACE_REC709; + + fmt->colorspace = colorspace; + fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT; + fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + fmt->quantization = V4L2_QUANTIZATION_DEFAULT; +} + static int coda_try_fmt_vid_out(struct file *file, void *priv, struct v4l2_format *f) { @@ -648,16 +667,8 @@ static int coda_try_fmt_vid_out(struct file *file, void *priv, if (ret < 0) return ret; - switch (f->fmt.pix.colorspace) { - case V4L2_COLORSPACE_REC709: - case V4L2_COLORSPACE_JPEG: - break; - default: - if (f->fmt.pix.pixelformat == V4L2_PIX_FMT_JPEG) - f->fmt.pix.colorspace = V4L2_COLORSPACE_JPEG; - else - f->fmt.pix.colorspace = V4L2_COLORSPACE_REC709; - } + if (f->fmt.pix.colorspace == V4L2_COLORSPACE_DEFAULT) + coda_set_default_colorspace(&f->fmt.pix); q_data_dst = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); codec = coda_find_codec(dev, f->fmt.pix.pixelformat, q_data_dst->fourcc); @@ -772,6 +783,9 @@ static int coda_s_fmt_vid_out(struct file *file, void *priv, return ret; ctx->colorspace = f->fmt.pix.colorspace; + ctx->xfer_func = f->fmt.pix.xfer_func; + ctx->ycbcr_enc = f->fmt.pix.ycbcr_enc; + ctx->quantization = f->fmt.pix.quantization; memset(&f_cap, 0, sizeof(f_cap)); f_cap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; @@ -1282,7 +1296,13 @@ static void set_default_params(struct coda_ctx *ctx) csize = coda_estimate_sizeimage(ctx, usize, max_w, max_h); ctx->params.codec_mode = ctx->codec->mode; - ctx->colorspace = V4L2_COLORSPACE_REC709; + if (ctx->cvd->src_formats[0] == V4L2_PIX_FMT_JPEG) + ctx->colorspace = V4L2_COLORSPACE_JPEG; + else + ctx->colorspace = V4L2_COLORSPACE_REC709; + ctx->xfer_func = V4L2_XFER_FUNC_DEFAULT; + ctx->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + ctx->quantization = V4L2_QUANTIZATION_DEFAULT; ctx->params.framerate = 30; /* Default formats for output and input queues */ @@ -1680,6 +1700,9 @@ static int coda_s_ctrl(struct v4l2_ctrl *ctrl) case V4L2_CID_MPEG_VIDEO_CYCLIC_INTRA_REFRESH_MB: ctx->params.intra_refresh = ctrl->val; break; + case V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME: + ctx->params.force_ipicture = true; + break; case V4L2_CID_JPEG_COMPRESSION_QUALITY: coda_set_jpeg_compression_quality(ctx, ctrl->val); break; @@ -2063,8 +2086,7 @@ static int coda_hw_init(struct coda_dev *dev) if (ret) goto err_clk_ahb; - if (dev->rstc) - reset_control_reset(dev->rstc); + reset_control_reset(dev->rstc); /* * Copy the first CODA_ISRAM_SIZE in the internal SRAM. @@ -2448,13 +2470,8 @@ static int coda_probe(struct platform_device *pdev) dev->rstc = devm_reset_control_get_optional(&pdev->dev, NULL); if (IS_ERR(dev->rstc)) { ret = PTR_ERR(dev->rstc); - if (ret == -ENOENT || ret == -ENOTSUPP) { - dev->rstc = NULL; - } else { - dev_err(&pdev->dev, "failed get reset control: %d\n", - ret); - return ret; - } + dev_err(&pdev->dev, "failed get reset control: %d\n", ret); + return ret; } /* Get IRAM pool from device tree or platform data */ diff --git a/drivers/media/platform/coda/coda.h b/drivers/media/platform/coda/coda.h index 20222befb9b2..76d059431ca1 100644 --- a/drivers/media/platform/coda/coda.h +++ b/drivers/media/platform/coda/coda.h @@ -135,6 +135,7 @@ struct coda_params { u32 vbv_size; u32 slice_max_bits; u32 slice_max_mb; + bool force_ipicture; }; struct coda_buffer_meta { @@ -206,6 +207,9 @@ struct coda_ctx { enum coda_inst_type inst_type; const struct coda_codec *codec; enum v4l2_colorspace colorspace; + enum v4l2_xfer_func xfer_func; + enum v4l2_ycbcr_encoding ycbcr_enc; + enum v4l2_quantization quantization; struct coda_params params; struct v4l2_ctrl_handler ctrls; struct v4l2_fh fh; diff --git a/drivers/media/platform/davinci/Kconfig b/drivers/media/platform/davinci/Kconfig index 554e710de487..55982e681d77 100644 --- a/drivers/media/platform/davinci/Kconfig +++ b/drivers/media/platform/davinci/Kconfig @@ -22,6 +22,7 @@ config VIDEO_DAVINCI_VPIF_CAPTURE depends on HAS_DMA depends on I2C select VIDEOBUF2_DMA_CONTIG + select V4L2_FWNODE help Enables Davinci VPIF module used for capture devices. This module is used for capture on TI DM6467/DA850/OMAPL138 diff --git a/drivers/media/platform/davinci/vpif_capture.c b/drivers/media/platform/davinci/vpif_capture.c index 44f702752d3a..d78580f9e431 100644 --- a/drivers/media/platform/davinci/vpif_capture.c +++ b/drivers/media/platform/davinci/vpif_capture.c @@ -18,10 +18,16 @@ #include <linux/module.h> #include <linux/interrupt.h> +#include <linux/of_graph.h> #include <linux/platform_device.h> #include <linux/slab.h> +#include <media/v4l2-fwnode.h> #include <media/v4l2-ioctl.h> +#include <media/i2c/tvp514x.h> +#include <media/v4l2-mediabus.h> + +#include <linux/videodev2.h> #include "vpif.h" #include "vpif_capture.h" @@ -385,7 +391,8 @@ static irqreturn_t vpif_channel_isr(int irq, void *dev_id) common = &ch->common[i]; /* skip If streaming is not started in this channel */ /* Check the field format */ - if (1 == ch->vpifparams.std_info.frm_fmt) { + if (1 == ch->vpifparams.std_info.frm_fmt || + common->fmt.fmt.pix.field == V4L2_FIELD_NONE) { /* Progressive mode */ spin_lock(&common->irqlock); if (list_empty(&common->dma_queue)) { @@ -466,9 +473,38 @@ static int vpif_update_std_info(struct channel_obj *ch) struct vpif_channel_config_params *std_info = &vpifparams->std_info; struct video_obj *vid_ch = &ch->video; int index; + struct v4l2_pix_format *pixfmt = &common->fmt.fmt.pix; vpif_dbg(2, debug, "vpif_update_std_info\n"); + /* + * if called after try_fmt or g_fmt, there will already be a size + * so use that by default. + */ + if (pixfmt->width && pixfmt->height) { + if (pixfmt->field == V4L2_FIELD_ANY || + pixfmt->field == V4L2_FIELD_NONE) + pixfmt->field = V4L2_FIELD_NONE; + + vpifparams->iface.if_type = VPIF_IF_BT656; + if (pixfmt->pixelformat == V4L2_PIX_FMT_SGRBG10 || + pixfmt->pixelformat == V4L2_PIX_FMT_SBGGR8) + vpifparams->iface.if_type = VPIF_IF_RAW_BAYER; + + if (pixfmt->pixelformat == V4L2_PIX_FMT_SGRBG10) + vpifparams->params.data_sz = 1; /* 10 bits/pixel. */ + + /* + * For raw formats from camera sensors, we don't need + * the std_info from table lookup, so nothing else to do here. + */ + if (vpifparams->iface.if_type == VPIF_IF_RAW_BAYER) { + memset(std_info, 0, sizeof(struct vpif_channel_config_params)); + vpifparams->std_info.capture_format = 1; /* CCD/raw mode */ + return 0; + } + } + for (index = 0; index < vpif_ch_params_count; index++) { config = &vpif_ch_params[index]; if (config->hd_sd == 0) { @@ -513,7 +549,7 @@ static int vpif_update_std_info(struct channel_obj *ch) if (ch->vpifparams.iface.if_type == VPIF_IF_RAW_BAYER) common->fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_SBGGR8; else - common->fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV422P; + common->fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_NV16; common->fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; @@ -655,7 +691,7 @@ static int vpif_input_to_subdev( /* loop through the sub device list to get the sub device info */ for (i = 0; i < vpif_cfg->subdev_count; i++) { subdev_info = &vpif_cfg->subdev_info[i]; - if (!strcmp(subdev_info->name, subdev_name)) + if (subdev_info && !strcmp(subdev_info->name, subdev_name)) return i; } return -1; @@ -917,8 +953,8 @@ static int vpif_enum_fmt_vid_cap(struct file *file, void *priv, fmt->pixelformat = V4L2_PIX_FMT_SBGGR8; } else { fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - strcpy(fmt->description, "YCbCr4:2:2 YC Planar"); - fmt->pixelformat = V4L2_PIX_FMT_YUV422P; + strcpy(fmt->description, "YCbCr4:2:2 Semi-Planar"); + fmt->pixelformat = V4L2_PIX_FMT_NV16; } return 0; } @@ -936,22 +972,8 @@ static int vpif_try_fmt_vid_cap(struct file *file, void *priv, struct channel_obj *ch = video_get_drvdata(vdev); struct v4l2_pix_format *pixfmt = &fmt->fmt.pix; struct common_obj *common = &(ch->common[VPIF_VIDEO_INDEX]); - struct vpif_params *vpif_params = &ch->vpifparams; - - /* - * to supress v4l-compliance warnings silently correct - * the pixelformat - */ - if (vpif_params->iface.if_type == VPIF_IF_RAW_BAYER) { - if (pixfmt->pixelformat != V4L2_PIX_FMT_SBGGR8) - pixfmt->pixelformat = V4L2_PIX_FMT_SBGGR8; - } else { - if (pixfmt->pixelformat != V4L2_PIX_FMT_YUV422P) - pixfmt->pixelformat = V4L2_PIX_FMT_YUV422P; - } - - common->fmt.fmt.pix.pixelformat = pixfmt->pixelformat; + common->fmt = *fmt; vpif_update_std_info(ch); pixfmt->field = common->fmt.fmt.pix.field; @@ -960,8 +982,17 @@ static int vpif_try_fmt_vid_cap(struct file *file, void *priv, pixfmt->width = common->fmt.fmt.pix.width; pixfmt->height = common->fmt.fmt.pix.height; pixfmt->sizeimage = pixfmt->bytesperline * pixfmt->height * 2; + if (pixfmt->pixelformat == V4L2_PIX_FMT_SGRBG10) { + pixfmt->bytesperline = common->fmt.fmt.pix.width * 2; + pixfmt->sizeimage = pixfmt->bytesperline * pixfmt->height; + } pixfmt->priv = 0; + dev_dbg(vpif_dev, "%s: %d x %d; pitch=%d pixelformat=0x%08x, field=%d, size=%d\n", __func__, + pixfmt->width, pixfmt->height, + pixfmt->bytesperline, pixfmt->pixelformat, + pixfmt->field, pixfmt->sizeimage); + return 0; } @@ -978,13 +1009,47 @@ static int vpif_g_fmt_vid_cap(struct file *file, void *priv, struct video_device *vdev = video_devdata(file); struct channel_obj *ch = video_get_drvdata(vdev); struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + struct v4l2_pix_format *pix_fmt = &fmt->fmt.pix; + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + struct v4l2_mbus_framefmt *mbus_fmt = &format.format; + int ret; /* Check the validity of the buffer type */ if (common->fmt.type != fmt->type) return -EINVAL; - /* Fill in the information about format */ + /* By default, use currently set fmt */ *fmt = common->fmt; + + /* If subdev has get_fmt, use that to override */ + ret = v4l2_subdev_call(ch->sd, pad, get_fmt, NULL, &format); + if (!ret && mbus_fmt->code) { + v4l2_fill_pix_format(pix_fmt, mbus_fmt); + pix_fmt->bytesperline = pix_fmt->width; + if (mbus_fmt->code == MEDIA_BUS_FMT_SGRBG10_1X10) { + /* e.g. mt9v032 */ + pix_fmt->pixelformat = V4L2_PIX_FMT_SGRBG10; + pix_fmt->bytesperline = pix_fmt->width * 2; + } else if (mbus_fmt->code == MEDIA_BUS_FMT_UYVY8_2X8) { + /* e.g. tvp514x */ + pix_fmt->pixelformat = V4L2_PIX_FMT_NV16; + pix_fmt->bytesperline = pix_fmt->width * 2; + } else { + dev_warn(vpif_dev, "%s: Unhandled media-bus format 0x%x\n", + __func__, mbus_fmt->code); + } + pix_fmt->sizeimage = pix_fmt->bytesperline * pix_fmt->height; + dev_dbg(vpif_dev, "%s: %d x %d; pitch=%d, pixelformat=0x%08x, code=0x%x, field=%d, size=%d\n", __func__, + pix_fmt->width, pix_fmt->height, + pix_fmt->bytesperline, pix_fmt->pixelformat, + mbus_fmt->code, pix_fmt->field, pix_fmt->sizeimage); + + common->fmt = *fmt; + vpif_update_std_info(ch); + } + return 0; } @@ -1323,6 +1388,22 @@ static int vpif_async_bound(struct v4l2_async_notifier *notifier, { int i; + for (i = 0; i < vpif_obj.config->asd_sizes[0]; i++) { + struct v4l2_async_subdev *_asd = vpif_obj.config->asd[i]; + const struct fwnode_handle *fwnode = _asd->match.fwnode.fwnode; + + if (fwnode == subdev->fwnode) { + vpif_obj.sd[i] = subdev; + vpif_obj.config->chan_config->inputs[i].subdev_name = + (char *)to_of_node(subdev->fwnode)->full_name; + vpif_dbg(2, debug, + "%s: setting input %d subdev_name = %s\n", + __func__, i, + to_of_node(subdev->fwnode)->full_name); + return 0; + } + } + for (i = 0; i < vpif_obj.config->subdev_count; i++) if (!strcmp(vpif_obj.config->subdev_info[i].name, subdev->name)) { @@ -1356,6 +1437,7 @@ static int vpif_probe_complete(void) /* set initial format */ ch->video.stdid = V4L2_STD_525_60; memset(&ch->video.dv_timings, 0, sizeof(ch->video.dv_timings)); + common->fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; vpif_update_std_info(ch); /* Initialize vb2 queue */ @@ -1418,6 +1500,106 @@ static int vpif_async_complete(struct v4l2_async_notifier *notifier) return vpif_probe_complete(); } +static struct vpif_capture_config * +vpif_capture_get_pdata(struct platform_device *pdev) +{ + struct device_node *endpoint = NULL; + struct v4l2_fwnode_endpoint bus_cfg; + struct vpif_capture_config *pdata; + struct vpif_subdev_info *sdinfo; + struct vpif_capture_chan_config *chan; + unsigned int i; + + /* + * DT boot: OF node from parent device contains + * video ports & endpoints data. + */ + if (pdev->dev.parent && pdev->dev.parent->of_node) + pdev->dev.of_node = pdev->dev.parent->of_node; + if (!IS_ENABLED(CONFIG_OF) || !pdev->dev.of_node) + return pdev->dev.platform_data; + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return NULL; + pdata->subdev_info = + devm_kzalloc(&pdev->dev, sizeof(*pdata->subdev_info) * + VPIF_CAPTURE_NUM_CHANNELS, GFP_KERNEL); + + if (!pdata->subdev_info) + return NULL; + + for (i = 0; i < VPIF_CAPTURE_NUM_CHANNELS; i++) { + struct device_node *rem; + unsigned int flags; + int err; + + endpoint = of_graph_get_next_endpoint(pdev->dev.of_node, + endpoint); + if (!endpoint) + break; + + sdinfo = &pdata->subdev_info[i]; + chan = &pdata->chan_config[i]; + chan->inputs = devm_kzalloc(&pdev->dev, + sizeof(*chan->inputs) * + VPIF_CAPTURE_NUM_CHANNELS, + GFP_KERNEL); + + chan->input_count++; + chan->inputs[i].input.type = V4L2_INPUT_TYPE_CAMERA; + chan->inputs[i].input.std = V4L2_STD_ALL; + chan->inputs[i].input.capabilities = V4L2_IN_CAP_STD; + + err = v4l2_fwnode_endpoint_parse(of_fwnode_handle(endpoint), + &bus_cfg); + if (err) { + dev_err(&pdev->dev, "Could not parse the endpoint\n"); + goto done; + } + dev_dbg(&pdev->dev, "Endpoint %s, bus_width = %d\n", + endpoint->full_name, bus_cfg.bus.parallel.bus_width); + flags = bus_cfg.bus.parallel.flags; + + if (flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) + chan->vpif_if.hd_pol = 1; + + if (flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) + chan->vpif_if.vd_pol = 1; + + rem = of_graph_get_remote_port_parent(endpoint); + if (!rem) { + dev_dbg(&pdev->dev, "Remote device at %s not found\n", + endpoint->full_name); + goto done; + } + + dev_dbg(&pdev->dev, "Remote device %s, %s found\n", + rem->name, rem->full_name); + sdinfo->name = rem->full_name; + + pdata->asd[i] = devm_kzalloc(&pdev->dev, + sizeof(struct v4l2_async_subdev), + GFP_KERNEL); + if (!pdata->asd[i]) { + of_node_put(rem); + pdata = NULL; + goto done; + } + + pdata->asd[i]->match_type = V4L2_ASYNC_MATCH_FWNODE; + pdata->asd[i]->match.fwnode.fwnode = of_fwnode_handle(rem); + of_node_put(rem); + } + +done: + pdata->asd_sizes[0] = i; + pdata->subdev_count = i; + pdata->card_name = "DA850/OMAP-L138 Video Capture"; + + return pdata; +} + /** * vpif_probe : This function probes the vpif capture driver * @pdev: platform device pointer @@ -1434,6 +1616,12 @@ static __init int vpif_probe(struct platform_device *pdev) int res_idx = 0; int i, err; + pdev->dev.platform_data = vpif_capture_get_pdata(pdev); + if (!pdev->dev.platform_data) { + dev_warn(&pdev->dev, "Missing platform data. Giving up.\n"); + return -EINVAL; + } + if (!pdev->dev.platform_data) { dev_warn(&pdev->dev, "Missing platform data. Giving up.\n"); return -EINVAL; @@ -1474,7 +1662,7 @@ static __init int vpif_probe(struct platform_device *pdev) goto vpif_unregister; } - if (!vpif_obj.config->asd_sizes) { + if (!vpif_obj.config->asd_sizes[0]) { int i2c_id = vpif_obj.config->i2c_adapter_id; i2c_adap = i2c_get_adapter(i2c_id); diff --git a/drivers/media/platform/davinci/vpif_display.c b/drivers/media/platform/davinci/vpif_display.c index 7e5cf9923c8d..b5ac6ce626b3 100644 --- a/drivers/media/platform/davinci/vpif_display.c +++ b/drivers/media/platform/davinci/vpif_display.c @@ -1250,6 +1250,11 @@ static __init int vpif_probe(struct platform_device *pdev) return -EINVAL; } + if (!pdev->dev.platform_data) { + dev_warn(&pdev->dev, "Missing platform data. Giving up.\n"); + return -EINVAL; + } + vpif_dev = &pdev->dev; err = initialize_vpif(); diff --git a/drivers/media/platform/exynos-gsc/gsc-core.c b/drivers/media/platform/exynos-gsc/gsc-core.c index 59a634201830..0241168c85af 100644 --- a/drivers/media/platform/exynos-gsc/gsc-core.c +++ b/drivers/media/platform/exynos-gsc/gsc-core.c @@ -454,6 +454,7 @@ int gsc_try_fmt_mplane(struct gsc_ctx *ctx, struct v4l2_format *f) } else { min_w = variant->pix_min->target_rot_dis_w; min_h = variant->pix_min->target_rot_dis_h; + pix_mp->colorspace = ctx->out_colorspace; } pr_debug("mod_x: %d, mod_y: %d, max_w: %d, max_h = %d", @@ -472,10 +473,8 @@ int gsc_try_fmt_mplane(struct gsc_ctx *ctx, struct v4l2_format *f) pix_mp->num_planes = fmt->num_planes; - if (pix_mp->width >= 1280) /* HD */ - pix_mp->colorspace = V4L2_COLORSPACE_REC709; - else /* SD */ - pix_mp->colorspace = V4L2_COLORSPACE_SMPTE170M; + if (V4L2_TYPE_IS_OUTPUT(f->type)) + ctx->out_colorspace = pix_mp->colorspace; for (i = 0; i < pix_mp->num_planes; ++i) { struct v4l2_plane_pix_format *plane_fmt = &pix_mp->plane_fmt[i]; @@ -519,8 +518,8 @@ int gsc_g_fmt_mplane(struct gsc_ctx *ctx, struct v4l2_format *f) pix_mp->height = frame->f_height; pix_mp->field = V4L2_FIELD_NONE; pix_mp->pixelformat = frame->fmt->pixelformat; - pix_mp->colorspace = V4L2_COLORSPACE_REC709; pix_mp->num_planes = frame->fmt->num_planes; + pix_mp->colorspace = ctx->out_colorspace; for (i = 0; i < pix_mp->num_planes; ++i) { pix_mp->plane_fmt[i].bytesperline = (frame->f_width * diff --git a/drivers/media/platform/exynos-gsc/gsc-core.h b/drivers/media/platform/exynos-gsc/gsc-core.h index 696217e9af66..715d9c9d8d30 100644 --- a/drivers/media/platform/exynos-gsc/gsc-core.h +++ b/drivers/media/platform/exynos-gsc/gsc-core.h @@ -376,6 +376,7 @@ struct gsc_ctx { struct v4l2_ctrl_handler ctrl_handler; struct gsc_ctrls gsc_ctrls; bool ctrls_rdy; + enum v4l2_colorspace out_colorspace; }; void gsc_set_prefbuf(struct gsc_dev *gsc, struct gsc_frame *frm); diff --git a/drivers/media/platform/exynos4-is/Kconfig b/drivers/media/platform/exynos4-is/Kconfig index 57d42c6172c5..c480efb755f5 100644 --- a/drivers/media/platform/exynos4-is/Kconfig +++ b/drivers/media/platform/exynos4-is/Kconfig @@ -4,6 +4,7 @@ config VIDEO_SAMSUNG_EXYNOS4_IS depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API depends on ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST depends on OF && COMMON_CLK + select V4L2_FWNODE help Say Y here to enable camera host interface devices for Samsung S5P and EXYNOS SoC series. @@ -32,6 +33,7 @@ config VIDEO_S5P_MIPI_CSIS tristate "S5P/EXYNOS MIPI-CSI2 receiver (MIPI-CSIS) driver" depends on REGULATOR select GENERIC_PHY + select V4L2_FWNODE help This is a V4L2 driver for Samsung S5P and EXYNOS4 SoC MIPI-CSI2 receiver (MIPI-CSIS) devices. diff --git a/drivers/media/platform/exynos4-is/fimc-capture.c b/drivers/media/platform/exynos4-is/fimc-capture.c index 8a7cd07dbe28..db60a63c0768 100644 --- a/drivers/media/platform/exynos4-is/fimc-capture.c +++ b/drivers/media/platform/exynos4-is/fimc-capture.c @@ -1277,6 +1277,7 @@ static int fimc_cap_g_selection(struct file *file, void *fh, case V4L2_SEL_TGT_COMPOSE_DEFAULT: case V4L2_SEL_TGT_COMPOSE_BOUNDS: f = &ctx->d_frame; + /* fall through */ case V4L2_SEL_TGT_CROP_BOUNDS: case V4L2_SEL_TGT_CROP_DEFAULT: s->r.left = 0; @@ -1287,6 +1288,7 @@ static int fimc_cap_g_selection(struct file *file, void *fh, case V4L2_SEL_TGT_COMPOSE: f = &ctx->d_frame; + /* fall through */ case V4L2_SEL_TGT_CROP: s->r.left = f->offs_h; s->r.top = f->offs_v; @@ -1610,6 +1612,7 @@ static int fimc_subdev_get_selection(struct v4l2_subdev *sd, switch (sel->target) { case V4L2_SEL_TGT_COMPOSE_BOUNDS: f = &ctx->d_frame; + /* fall through */ case V4L2_SEL_TGT_CROP_BOUNDS: r->width = f->o_width; r->height = f->o_height; diff --git a/drivers/media/platform/exynos4-is/fimc-is.c b/drivers/media/platform/exynos4-is/fimc-is.c index 7f92144a1de3..340d906db370 100644 --- a/drivers/media/platform/exynos4-is/fimc-is.c +++ b/drivers/media/platform/exynos4-is/fimc-is.c @@ -854,7 +854,7 @@ static int fimc_is_probe(struct platform_device *pdev) vb2_dma_contig_set_max_seg_size(dev, DMA_BIT_MASK(32)); - ret = of_platform_populate(dev->of_node, NULL, NULL, dev); + ret = devm_of_platform_populate(dev); if (ret < 0) goto err_pm; @@ -864,7 +864,7 @@ static int fimc_is_probe(struct platform_device *pdev) */ ret = fimc_is_register_subdevs(is); if (ret < 0) - goto err_of_dep; + goto err_pm; ret = fimc_is_debugfs_create(is); if (ret < 0) @@ -883,8 +883,6 @@ err_dfs: fimc_is_debugfs_remove(is); err_sd: fimc_is_unregister_subdevs(is); -err_of_dep: - of_platform_depopulate(dev); err_pm: if (!pm_runtime_enabled(dev)) fimc_is_runtime_suspend(dev); @@ -946,7 +944,6 @@ static int fimc_is_remove(struct platform_device *pdev) if (!pm_runtime_status_suspended(dev)) fimc_is_runtime_suspend(dev); free_irq(is->irq, is); - of_platform_depopulate(dev); fimc_is_unregister_subdevs(is); vb2_dma_contig_clear_max_seg_size(dev); fimc_is_put_clocks(is); diff --git a/drivers/media/platform/exynos4-is/media-dev.c b/drivers/media/platform/exynos4-is/media-dev.c index e82450e90a67..7d1cf78846c4 100644 --- a/drivers/media/platform/exynos4-is/media-dev.c +++ b/drivers/media/platform/exynos4-is/media-dev.c @@ -29,7 +29,7 @@ #include <linux/slab.h> #include <media/v4l2-async.h> #include <media/v4l2-ctrls.h> -#include <media/v4l2-of.h> +#include <media/v4l2-fwnode.h> #include <media/media-device.h> #include <media/drv-intf/exynos-fimc.h> @@ -388,7 +388,7 @@ static int fimc_md_parse_port_node(struct fimc_md *fmd, { struct fimc_source_info *pd = &fmd->sensor[index].pdata; struct device_node *rem, *ep, *np; - struct v4l2_of_endpoint endpoint; + struct v4l2_fwnode_endpoint endpoint; int ret; /* Assume here a port node can have only one endpoint node. */ @@ -396,7 +396,7 @@ static int fimc_md_parse_port_node(struct fimc_md *fmd, if (!ep) return 0; - ret = v4l2_of_parse_endpoint(ep, &endpoint); + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &endpoint); if (ret) { of_node_put(ep); return ret; @@ -453,8 +453,8 @@ static int fimc_md_parse_port_node(struct fimc_md *fmd, return -EINVAL; } - fmd->sensor[index].asd.match_type = V4L2_ASYNC_MATCH_OF; - fmd->sensor[index].asd.match.of.node = rem; + fmd->sensor[index].asd.match_type = V4L2_ASYNC_MATCH_FWNODE; + fmd->sensor[index].asd.match.fwnode.fwnode = of_fwnode_handle(rem); fmd->async_subdevs[index] = &fmd->sensor[index].asd; fmd->num_sensors++; @@ -1361,7 +1361,8 @@ static int subdev_notifier_bound(struct v4l2_async_notifier *notifier, /* Find platform data for this sensor subdev */ for (i = 0; i < ARRAY_SIZE(fmd->sensor); i++) - if (fmd->sensor[i].asd.match.of.node == subdev->dev->of_node) + if (fmd->sensor[i].asd.match.fwnode.fwnode == + of_fwnode_handle(subdev->dev->of_node)) si = &fmd->sensor[i]; if (si == NULL) diff --git a/drivers/media/platform/exynos4-is/mipi-csis.c b/drivers/media/platform/exynos4-is/mipi-csis.c index f819b29efc38..98c89873c2dc 100644 --- a/drivers/media/platform/exynos4-is/mipi-csis.c +++ b/drivers/media/platform/exynos4-is/mipi-csis.c @@ -30,7 +30,7 @@ #include <linux/spinlock.h> #include <linux/videodev2.h> #include <media/drv-intf/exynos-fimc.h> -#include <media/v4l2-of.h> +#include <media/v4l2-fwnode.h> #include <media/v4l2-subdev.h> #include "mipi-csis.h" @@ -718,7 +718,7 @@ static int s5pcsis_parse_dt(struct platform_device *pdev, struct csis_state *state) { struct device_node *node = pdev->dev.of_node; - struct v4l2_of_endpoint endpoint; + struct v4l2_fwnode_endpoint endpoint; int ret; if (of_property_read_u32(node, "clock-frequency", @@ -735,7 +735,7 @@ static int s5pcsis_parse_dt(struct platform_device *pdev, return -EINVAL; } /* Get port node and validate MIPI-CSI channel id. */ - ret = v4l2_of_parse_endpoint(node, &endpoint); + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(node), &endpoint); if (ret) goto err; diff --git a/drivers/media/platform/marvell-ccic/mcam-core.c b/drivers/media/platform/marvell-ccic/mcam-core.c index a8bda6679422..8cac2f202099 100644 --- a/drivers/media/platform/marvell-ccic/mcam-core.c +++ b/drivers/media/platform/marvell-ccic/mcam-core.c @@ -393,6 +393,7 @@ static int mcam_alloc_dma_bufs(struct mcam_camera *cam, int loadtime) dma_free_coherent(cam->dev, cam->dma_buf_size, cam->dma_bufs[0], cam->dma_handles[0]); cam->nbufs = 0; + /* fall-through */ case 0: cam_err(cam, "Insufficient DMA buffers, cannot operate\n"); return -ENOMEM; diff --git a/drivers/media/platform/mtk-mdp/mtk_mdp_core.c b/drivers/media/platform/mtk-mdp/mtk_mdp_core.c index 9e4eb7dcc424..81347558b24a 100644 --- a/drivers/media/platform/mtk-mdp/mtk_mdp_core.c +++ b/drivers/media/platform/mtk-mdp/mtk_mdp_core.c @@ -103,7 +103,7 @@ static int mtk_mdp_probe(struct platform_device *pdev) { struct mtk_mdp_dev *mdp; struct device *dev = &pdev->dev; - struct device_node *node; + struct device_node *node, *parent; int i, ret = 0; mdp = devm_kzalloc(dev, sizeof(*mdp), GFP_KERNEL); @@ -117,8 +117,16 @@ static int mtk_mdp_probe(struct platform_device *pdev) mutex_init(&mdp->lock); mutex_init(&mdp->vpulock); + /* Old dts had the components as child nodes */ + if (of_get_next_child(dev->of_node, NULL)) { + parent = dev->of_node; + dev_warn(dev, "device tree is out of date\n"); + } else { + parent = dev->of_node->parent; + } + /* Iterate over sibling MDP function blocks */ - for_each_child_of_node(dev->of_node, node) { + for_each_child_of_node(parent, node) { const struct of_device_id *of_id; enum mtk_mdp_comp_type comp_type; int comp_id; diff --git a/drivers/media/platform/mtk-vcodec/mtk_vcodec_dec.c b/drivers/media/platform/mtk-vcodec/mtk_vcodec_dec.c index a60b538686ea..843510979ad8 100644 --- a/drivers/media/platform/mtk-vcodec/mtk_vcodec_dec.c +++ b/drivers/media/platform/mtk-vcodec/mtk_vcodec_dec.c @@ -278,7 +278,7 @@ static void mtk_vdec_flush_decoder(struct mtk_vcodec_ctx *ctx) clean_free_buffer(ctx); } -static void mtk_vdec_pic_info_update(struct mtk_vcodec_ctx *ctx) +static int mtk_vdec_pic_info_update(struct mtk_vcodec_ctx *ctx) { unsigned int dpbsize = 0; int ret; @@ -288,7 +288,7 @@ static void mtk_vdec_pic_info_update(struct mtk_vcodec_ctx *ctx) &ctx->last_decoded_picinfo)) { mtk_v4l2_err("[%d]Error!! Cannot get param : GET_PARAM_PICTURE_INFO ERR", ctx->id); - return; + return -EINVAL; } if (ctx->last_decoded_picinfo.pic_w == 0 || @@ -296,12 +296,12 @@ static void mtk_vdec_pic_info_update(struct mtk_vcodec_ctx *ctx) ctx->last_decoded_picinfo.buf_w == 0 || ctx->last_decoded_picinfo.buf_h == 0) { mtk_v4l2_err("Cannot get correct pic info"); - return; + return -EINVAL; } if ((ctx->last_decoded_picinfo.pic_w == ctx->picinfo.pic_w) || (ctx->last_decoded_picinfo.pic_h == ctx->picinfo.pic_h)) - return; + return 0; mtk_v4l2_debug(1, "[%d]-> new(%d,%d), old(%d,%d), real(%d,%d)", @@ -316,6 +316,8 @@ static void mtk_vdec_pic_info_update(struct mtk_vcodec_ctx *ctx) mtk_v4l2_err("Incorrect dpb size, ret=%d", ret); ctx->dpb_size = dpbsize; + + return ret; } static void mtk_vdec_worker(struct work_struct *work) diff --git a/drivers/media/platform/mtk-vcodec/mtk_vcodec_util.h b/drivers/media/platform/mtk-vcodec/mtk_vcodec_util.h index 237e144c194f..06c254f5c171 100644 --- a/drivers/media/platform/mtk-vcodec/mtk_vcodec_util.h +++ b/drivers/media/platform/mtk-vcodec/mtk_vcodec_util.h @@ -32,6 +32,15 @@ extern int mtk_v4l2_dbg_level; extern bool mtk_vcodec_dbg; +#define mtk_v4l2_err(fmt, args...) \ + pr_err("[MTK_V4L2][ERROR] %s:%d: " fmt "\n", __func__, __LINE__, \ + ##args) + +#define mtk_vcodec_err(h, fmt, args...) \ + pr_err("[MTK_VCODEC][ERROR][%d]: %s() " fmt "\n", \ + ((struct mtk_vcodec_ctx *)h->ctx)->id, __func__, ##args) + + #if defined(DEBUG) #define mtk_v4l2_debug(level, fmt, args...) \ @@ -41,11 +50,6 @@ extern bool mtk_vcodec_dbg; level, __func__, __LINE__, ##args); \ } while (0) -#define mtk_v4l2_err(fmt, args...) \ - pr_err("[MTK_V4L2][ERROR] %s:%d: " fmt "\n", __func__, __LINE__, \ - ##args) - - #define mtk_v4l2_debug_enter() mtk_v4l2_debug(3, "+") #define mtk_v4l2_debug_leave() mtk_v4l2_debug(3, "-") @@ -57,22 +61,16 @@ extern bool mtk_vcodec_dbg; __func__, ##args); \ } while (0) -#define mtk_vcodec_err(h, fmt, args...) \ - pr_err("[MTK_VCODEC][ERROR][%d]: %s() " fmt "\n", \ - ((struct mtk_vcodec_ctx *)h->ctx)->id, __func__, ##args) - #define mtk_vcodec_debug_enter(h) mtk_vcodec_debug(h, "+") #define mtk_vcodec_debug_leave(h) mtk_vcodec_debug(h, "-") #else #define mtk_v4l2_debug(level, fmt, args...) {} -#define mtk_v4l2_err(fmt, args...) {} #define mtk_v4l2_debug_enter() {} #define mtk_v4l2_debug_leave() {} #define mtk_vcodec_debug(h, fmt, args...) {} -#define mtk_vcodec_err(h, fmt, args...) {} #define mtk_vcodec_debug_enter(h) {} #define mtk_vcodec_debug_leave(h) {} diff --git a/drivers/media/platform/omap3isp/isp.c b/drivers/media/platform/omap3isp/isp.c index 0d984a28a003..9df64c189883 100644 --- a/drivers/media/platform/omap3isp/isp.c +++ b/drivers/media/platform/omap3isp/isp.c @@ -55,6 +55,7 @@ #include <linux/module.h> #include <linux/omap-iommu.h> #include <linux/platform_device.h> +#include <linux/property.h> #include <linux/regulator/consumer.h> #include <linux/slab.h> #include <linux/sched.h> @@ -63,9 +64,9 @@ #include <asm/dma-iommu.h> #include <media/v4l2-common.h> +#include <media/v4l2-fwnode.h> #include <media/v4l2-device.h> #include <media/v4l2-mc.h> -#include <media/v4l2-of.h> #include "isp.h" #include "ispreg.h" @@ -2007,20 +2008,20 @@ enum isp_of_phy { ISP_OF_PHY_CSIPHY2, }; -static int isp_of_parse_node(struct device *dev, struct device_node *node, - struct isp_async_subdev *isd) +static int isp_fwnode_parse(struct device *dev, struct fwnode_handle *fwnode, + struct isp_async_subdev *isd) { struct isp_bus_cfg *buscfg = &isd->bus; - struct v4l2_of_endpoint vep; + struct v4l2_fwnode_endpoint vep; unsigned int i; int ret; - ret = v4l2_of_parse_endpoint(node, &vep); + ret = v4l2_fwnode_endpoint_parse(fwnode, &vep); if (ret) return ret; - dev_dbg(dev, "parsing endpoint %s, interface %u\n", node->full_name, - vep.base.port); + dev_dbg(dev, "parsing endpoint %s, interface %u\n", + to_of_node(fwnode)->full_name, vep.base.port); switch (vep.base.port) { case ISP_OF_PHY_PARALLEL: @@ -2077,18 +2078,18 @@ static int isp_of_parse_node(struct device *dev, struct device_node *node, break; default: - dev_warn(dev, "%s: invalid interface %u\n", node->full_name, - vep.base.port); + dev_warn(dev, "%s: invalid interface %u\n", + to_of_node(fwnode)->full_name, vep.base.port); break; } return 0; } -static int isp_of_parse_nodes(struct device *dev, - struct v4l2_async_notifier *notifier) +static int isp_fwnodes_parse(struct device *dev, + struct v4l2_async_notifier *notifier) { - struct device_node *node = NULL; + struct fwnode_handle *fwnode = NULL; notifier->subdevs = devm_kcalloc( dev, ISP_MAX_SUBDEVS, sizeof(*notifier->subdevs), GFP_KERNEL); @@ -2096,7 +2097,8 @@ static int isp_of_parse_nodes(struct device *dev, return -ENOMEM; while (notifier->num_subdevs < ISP_MAX_SUBDEVS && - (node = of_graph_get_next_endpoint(dev->of_node, node))) { + (fwnode = fwnode_graph_get_next_endpoint( + of_fwnode_handle(dev->of_node), fwnode))) { struct isp_async_subdev *isd; isd = devm_kzalloc(dev, sizeof(*isd), GFP_KERNEL); @@ -2105,23 +2107,24 @@ static int isp_of_parse_nodes(struct device *dev, notifier->subdevs[notifier->num_subdevs] = &isd->asd; - if (isp_of_parse_node(dev, node, isd)) + if (isp_fwnode_parse(dev, fwnode, isd)) goto error; - isd->asd.match.of.node = of_graph_get_remote_port_parent(node); - if (!isd->asd.match.of.node) { + isd->asd.match.fwnode.fwnode = + fwnode_graph_get_remote_port_parent(fwnode); + if (!isd->asd.match.fwnode.fwnode) { dev_warn(dev, "bad remote port parent\n"); goto error; } - isd->asd.match_type = V4L2_ASYNC_MATCH_OF; + isd->asd.match_type = V4L2_ASYNC_MATCH_FWNODE; notifier->num_subdevs++; } return notifier->num_subdevs; error: - of_node_put(node); + fwnode_handle_put(fwnode); return -EINVAL; } @@ -2192,8 +2195,8 @@ static int isp_probe(struct platform_device *pdev) return -ENOMEM; } - ret = of_property_read_u32(pdev->dev.of_node, "ti,phy-type", - &isp->phy_type); + ret = fwnode_property_read_u32(of_fwnode_handle(pdev->dev.of_node), + "ti,phy-type", &isp->phy_type); if (ret) return ret; @@ -2202,12 +2205,12 @@ static int isp_probe(struct platform_device *pdev) if (IS_ERR(isp->syscon)) return PTR_ERR(isp->syscon); - ret = of_property_read_u32_index(pdev->dev.of_node, "syscon", 1, - &isp->syscon_offset); + ret = of_property_read_u32_index(pdev->dev.of_node, + "syscon", 1, &isp->syscon_offset); if (ret) return ret; - ret = isp_of_parse_nodes(&pdev->dev, &isp->notifier); + ret = isp_fwnodes_parse(&pdev->dev, &isp->notifier); if (ret < 0) return ret; diff --git a/drivers/media/platform/pxa_camera.c b/drivers/media/platform/pxa_camera.c index 929006f65cc7..399095170b6e 100644 --- a/drivers/media/platform/pxa_camera.c +++ b/drivers/media/platform/pxa_camera.c @@ -25,6 +25,7 @@ #include <linux/mm.h> #include <linux/moduleparam.h> #include <linux/of.h> +#include <linux/of_graph.h> #include <linux/time.h> #include <linux/platform_device.h> #include <linux/clk.h> @@ -37,9 +38,11 @@ #include <media/v4l2-async.h> #include <media/v4l2-clk.h> #include <media/v4l2-common.h> +#include <media/v4l2-ctrls.h> #include <media/v4l2-device.h> +#include <media/v4l2-event.h> #include <media/v4l2-ioctl.h> -#include <media/v4l2-of.h> +#include <media/v4l2-fwnode.h> #include <media/videobuf2-dma-sg.h> @@ -345,6 +348,36 @@ static const struct pxa_mbus_lookup mbus_fmt[] = { .layout = PXA_MBUS_LAYOUT_PACKED, }, }, { + .code = MEDIA_BUS_FMT_SGBRG8_1X8, + .fmt = { + .fourcc = V4L2_PIX_FMT_SGBRG8, + .name = "Bayer 8 GBRG", + .bits_per_sample = 8, + .packing = PXA_MBUS_PACKING_NONE, + .order = PXA_MBUS_ORDER_LE, + .layout = PXA_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_SGRBG8_1X8, + .fmt = { + .fourcc = V4L2_PIX_FMT_SGRBG8, + .name = "Bayer 8 GRBG", + .bits_per_sample = 8, + .packing = PXA_MBUS_PACKING_NONE, + .order = PXA_MBUS_ORDER_LE, + .layout = PXA_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_SRGGB8_1X8, + .fmt = { + .fourcc = V4L2_PIX_FMT_SRGGB8, + .name = "Bayer 8 RGGB", + .bits_per_sample = 8, + .packing = PXA_MBUS_PACKING_NONE, + .order = PXA_MBUS_ORDER_LE, + .layout = PXA_MBUS_LAYOUT_PACKED, + }, +}, { .code = MEDIA_BUS_FMT_SBGGR10_1X10, .fmt = { .fourcc = V4L2_PIX_FMT_SBGGR10, @@ -445,16 +478,6 @@ static const struct pxa_mbus_lookup mbus_fmt[] = { .layout = PXA_MBUS_LAYOUT_PACKED, }, }, { - .code = MEDIA_BUS_FMT_SGRBG8_1X8, - .fmt = { - .fourcc = V4L2_PIX_FMT_SGRBG8, - .name = "Bayer 8 GRBG", - .bits_per_sample = 8, - .packing = PXA_MBUS_PACKING_NONE, - .order = PXA_MBUS_ORDER_LE, - .layout = PXA_MBUS_LAYOUT_PACKED, - }, -}, { .code = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8, .fmt = { .fourcc = V4L2_PIX_FMT_SGRBG10DPCM8, @@ -555,6 +578,9 @@ static s32 pxa_mbus_bytes_per_line(u32 width, const struct pxa_mbus_pixelfmt *mf static s32 pxa_mbus_image_size(const struct pxa_mbus_pixelfmt *mf, u32 bytes_per_line, u32 height) { + if (mf->layout == PXA_MBUS_LAYOUT_PACKED) + return bytes_per_line * height; + switch (mf->packing) { case PXA_MBUS_PACKING_2X8_PADHI: return bytes_per_line * height * 2; @@ -1099,7 +1125,7 @@ static u32 mclk_get_divisor(struct platform_device *pdev, /* mclk <= ciclk / 4 (27.4.2) */ if (mclk > lcdclk / 4) { mclk = lcdclk / 4; - dev_warn(pcdev_to_dev(pcdev), + dev_warn(&pdev->dev, "Limiting master clock to %lu\n", mclk); } @@ -1110,7 +1136,7 @@ static u32 mclk_get_divisor(struct platform_device *pdev, if (pcdev->platform_flags & PXA_CAMERA_MCLK_EN) pcdev->mclk = lcdclk / (2 * (div + 1)); - dev_dbg(pcdev_to_dev(pcdev), "LCD clock %luHz, target freq %luHz, divisor %u\n", + dev_dbg(&pdev->dev, "LCD clock %luHz, target freq %luHz, divisor %u\n", lcdclk, mclk, div); return div; @@ -1291,6 +1317,7 @@ static void pxa_camera_setup_cicr(struct pxa_camera_dev *pcdev, * transformation. Note that UYVY is the only format that * should be used if pxa framebuffer Overlay2 is used. */ + /* fall through */ case V4L2_PIX_FMT_UYVY: case V4L2_PIX_FMT_VYUY: case V4L2_PIX_FMT_YUYV: @@ -2066,6 +2093,8 @@ static const struct v4l2_ioctl_ops pxa_camera_ioctl_ops = { .vidioc_g_register = pxac_vidioc_g_register, .vidioc_s_register = pxac_vidioc_s_register, #endif + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, }; static struct v4l2_clk_ops pxa_camera_mclk_ops = { @@ -2177,6 +2206,12 @@ static void pxa_camera_sensor_unbind(struct v4l2_async_notifier *notifier, pxa_dma_stop_channels(pcdev); pxa_camera_destroy_formats(pcdev); + + if (pcdev->mclk_clk) { + v4l2_clk_unregister(pcdev->mclk_clk); + pcdev->mclk_clk = NULL; + } + video_unregister_device(&pcdev->vdev); pcdev->sensor = NULL; @@ -2236,7 +2271,7 @@ static int pxa_camera_pdata_from_dt(struct device *dev, { u32 mclk_rate; struct device_node *remote, *np = dev->of_node; - struct v4l2_of_endpoint ep; + struct v4l2_fwnode_endpoint ep; int err = of_property_read_u32(np, "clock-frequency", &mclk_rate); if (!err) { @@ -2250,7 +2285,7 @@ static int pxa_camera_pdata_from_dt(struct device *dev, return -EINVAL; } - err = v4l2_of_parse_endpoint(np, &ep); + err = v4l2_fwnode_endpoint_parse(of_fwnode_handle(np), &ep); if (err) { dev_err(dev, "could not parse endpoint\n"); goto out; @@ -2287,10 +2322,10 @@ static int pxa_camera_pdata_from_dt(struct device *dev, if (ep.bus.parallel.flags & V4L2_MBUS_PCLK_SAMPLE_FALLING) pcdev->platform_flags |= PXA_CAMERA_PCLK_EN; - asd->match_type = V4L2_ASYNC_MATCH_OF; + asd->match_type = V4L2_ASYNC_MATCH_FWNODE; remote = of_graph_get_remote_port(np); if (remote) { - asd->match.of.node = remote; + asd->match.fwnode.fwnode = of_fwnode_handle(remote); of_node_put(remote); } else { dev_notice(dev, "no remote for %s\n", of_node_full_name(np)); @@ -2501,7 +2536,13 @@ static int pxa_camera_remove(struct platform_device *pdev) dma_release_channel(pcdev->dma_chans[1]); dma_release_channel(pcdev->dma_chans[2]); - v4l2_clk_unregister(pcdev->mclk_clk); + v4l2_async_notifier_unregister(&pcdev->notifier); + + if (pcdev->mclk_clk) { + v4l2_clk_unregister(pcdev->mclk_clk); + pcdev->mclk_clk = NULL; + } + v4l2_device_unregister(&pcdev->v4l2_dev); dev_info(&pdev->dev, "PXA Camera driver unloaded\n"); diff --git a/drivers/media/platform/rcar-vin/Kconfig b/drivers/media/platform/rcar-vin/Kconfig index 111d2a151f6a..af4c98b44d2e 100644 --- a/drivers/media/platform/rcar-vin/Kconfig +++ b/drivers/media/platform/rcar-vin/Kconfig @@ -3,6 +3,7 @@ config VIDEO_RCAR_VIN depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API && OF && HAS_DMA && MEDIA_CONTROLLER depends on ARCH_RENESAS || COMPILE_TEST select VIDEOBUF2_DMA_CONTIG + select V4L2_FWNODE ---help--- Support for Renesas R-Car Video Input (VIN) driver. Supports R-Car Gen2 SoCs. diff --git a/drivers/media/platform/rcar-vin/rcar-core.c b/drivers/media/platform/rcar-vin/rcar-core.c index 098a0b1cc10a..77dff047c41c 100644 --- a/drivers/media/platform/rcar-vin/rcar-core.c +++ b/drivers/media/platform/rcar-vin/rcar-core.c @@ -21,7 +21,7 @@ #include <linux/platform_device.h> #include <linux/pm_runtime.h> -#include <media/v4l2-of.h> +#include <media/v4l2-fwnode.h> #include "rcar-vin.h" @@ -31,6 +31,20 @@ #define notifier_to_vin(n) container_of(n, struct rvin_dev, notifier) +static int rvin_find_pad(struct v4l2_subdev *sd, int direction) +{ + unsigned int pad; + + if (sd->entity.num_pads <= 1) + return 0; + + for (pad = 0; pad < sd->entity.num_pads; pad++) + if (sd->entity.pads[pad].flags & direction) + return pad; + + return -EINVAL; +} + static bool rvin_mbus_supported(struct rvin_graph_entity *entity) { struct v4l2_subdev *sd = entity->subdev; @@ -39,6 +53,7 @@ static bool rvin_mbus_supported(struct rvin_graph_entity *entity) }; code.index = 0; + code.pad = entity->source_pad; while (!v4l2_subdev_call(sd, pad, enum_mbus_code, NULL, &code)) { code.index++; switch (code.code) { @@ -86,14 +101,9 @@ static void rvin_digital_notify_unbind(struct v4l2_async_notifier *notifier, { struct rvin_dev *vin = notifier_to_vin(notifier); - if (vin->digital.subdev == subdev) { - vin_dbg(vin, "unbind digital subdev %s\n", subdev->name); - rvin_v4l2_remove(vin); - vin->digital.subdev = NULL; - return; - } - - vin_err(vin, "no entity for subdev %s to unbind\n", subdev->name); + vin_dbg(vin, "unbind digital subdev %s\n", subdev->name); + rvin_v4l2_remove(vin); + vin->digital.subdev = NULL; } static int rvin_digital_notify_bound(struct v4l2_async_notifier *notifier, @@ -101,27 +111,37 @@ static int rvin_digital_notify_bound(struct v4l2_async_notifier *notifier, struct v4l2_async_subdev *asd) { struct rvin_dev *vin = notifier_to_vin(notifier); + int ret; v4l2_set_subdev_hostdata(subdev, vin); - if (vin->digital.asd.match.of.node == subdev->dev->of_node) { - vin_dbg(vin, "bound digital subdev %s\n", subdev->name); - vin->digital.subdev = subdev; - return 0; - } + /* Find source and sink pad of remote subdevice */ - vin_err(vin, "no entity for subdev %s to bind\n", subdev->name); - return -EINVAL; + ret = rvin_find_pad(subdev, MEDIA_PAD_FL_SOURCE); + if (ret < 0) + return ret; + vin->digital.source_pad = ret; + + ret = rvin_find_pad(subdev, MEDIA_PAD_FL_SINK); + vin->digital.sink_pad = ret < 0 ? 0 : ret; + + vin->digital.subdev = subdev; + + vin_dbg(vin, "bound subdev %s source pad: %u sink pad: %u\n", + subdev->name, vin->digital.source_pad, + vin->digital.sink_pad); + + return 0; } static int rvin_digitial_parse_v4l2(struct rvin_dev *vin, struct device_node *ep, struct v4l2_mbus_config *mbus_cfg) { - struct v4l2_of_endpoint v4l2_ep; + struct v4l2_fwnode_endpoint v4l2_ep; int ret; - ret = v4l2_of_parse_endpoint(ep, &v4l2_ep); + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &v4l2_ep); if (ret) { vin_err(vin, "Could not parse v4l2 endpoint\n"); return -EINVAL; @@ -151,7 +171,7 @@ static int rvin_digital_graph_parse(struct rvin_dev *vin) struct device_node *ep, *np; int ret; - vin->digital.asd.match.of.node = NULL; + vin->digital.asd.match.fwnode.fwnode = NULL; vin->digital.subdev = NULL; /* @@ -175,8 +195,8 @@ static int rvin_digital_graph_parse(struct rvin_dev *vin) if (ret) return ret; - vin->digital.asd.match.of.node = np; - vin->digital.asd.match_type = V4L2_ASYNC_MATCH_OF; + vin->digital.asd.match.fwnode.fwnode = of_fwnode_handle(np); + vin->digital.asd.match_type = V4L2_ASYNC_MATCH_FWNODE; return 0; } @@ -190,7 +210,7 @@ static int rvin_digital_graph_init(struct rvin_dev *vin) if (ret) return ret; - if (!vin->digital.asd.match.of.node) { + if (!vin->digital.asd.match.fwnode.fwnode) { vin_dbg(vin, "No digital subdevice found\n"); return -ENODEV; } @@ -203,7 +223,7 @@ static int rvin_digital_graph_init(struct rvin_dev *vin) subdevs[0] = &vin->digital.asd; vin_dbg(vin, "Found digital subdevice %s\n", - of_node_full_name(subdevs[0]->match.of.node)); + of_node_full_name(to_of_node(subdevs[0]->match.fwnode.fwnode))); vin->notifier.num_subdevs = 1; vin->notifier.subdevs = subdevs; diff --git a/drivers/media/platform/rcar-vin/rcar-dma.c b/drivers/media/platform/rcar-vin/rcar-dma.c index 9ccd5ff55e19..b136844499f6 100644 --- a/drivers/media/platform/rcar-vin/rcar-dma.c +++ b/drivers/media/platform/rcar-vin/rcar-dma.c @@ -119,6 +119,15 @@ #define VNDMR2_FTEV (1 << 17) #define VNDMR2_VLV(n) ((n & 0xf) << 12) +struct rvin_buffer { + struct vb2_v4l2_buffer vb; + struct list_head list; +}; + +#define to_buf_list(vb2_buffer) (&container_of(vb2_buffer, \ + struct rvin_buffer, \ + vb)->list) + static void rvin_write(struct rvin_dev *vin, u32 value, u32 offset) { iowrite32(value, vin->base + offset); @@ -269,48 +278,6 @@ static int rvin_setup(struct rvin_dev *vin) return 0; } -static void rvin_capture_on(struct rvin_dev *vin) -{ - vin_dbg(vin, "Capture on in %s mode\n", - vin->continuous ? "continuous" : "single"); - - if (vin->continuous) - /* Continuous Frame Capture Mode */ - rvin_write(vin, VNFC_C_FRAME, VNFC_REG); - else - /* Single Frame Capture Mode */ - rvin_write(vin, VNFC_S_FRAME, VNFC_REG); -} - -static void rvin_capture_off(struct rvin_dev *vin) -{ - /* Set continuous & single transfer off */ - rvin_write(vin, 0, VNFC_REG); -} - -static int rvin_capture_start(struct rvin_dev *vin) -{ - int ret; - - rvin_crop_scale_comp(vin); - - ret = rvin_setup(vin); - if (ret) - return ret; - - rvin_capture_on(vin); - - return 0; -} - -static void rvin_capture_stop(struct rvin_dev *vin) -{ - rvin_capture_off(vin); - - /* Disable module */ - rvin_write(vin, rvin_read(vin, VNMC_REG) & ~VNMC_ME, VNMC_REG); -} - static void rvin_disable_interrupts(struct rvin_dev *vin) { rvin_write(vin, 0, VNIE_REG); @@ -377,6 +344,99 @@ static void rvin_set_slot_addr(struct rvin_dev *vin, int slot, dma_addr_t addr) rvin_write(vin, offset, VNMB_REG(slot)); } +/* Moves a buffer from the queue to the HW slots */ +static bool rvin_fill_hw_slot(struct rvin_dev *vin, int slot) +{ + struct rvin_buffer *buf; + struct vb2_v4l2_buffer *vbuf; + dma_addr_t phys_addr_top; + + if (vin->queue_buf[slot] != NULL) + return true; + + if (list_empty(&vin->buf_list)) + return false; + + vin_dbg(vin, "Filling HW slot: %d\n", slot); + + /* Keep track of buffer we give to HW */ + buf = list_entry(vin->buf_list.next, struct rvin_buffer, list); + vbuf = &buf->vb; + list_del_init(to_buf_list(vbuf)); + vin->queue_buf[slot] = vbuf; + + /* Setup DMA */ + phys_addr_top = vb2_dma_contig_plane_dma_addr(&vbuf->vb2_buf, 0); + rvin_set_slot_addr(vin, slot, phys_addr_top); + + return true; +} + +static bool rvin_fill_hw(struct rvin_dev *vin) +{ + int slot, limit; + + limit = vin->continuous ? HW_BUFFER_NUM : 1; + + for (slot = 0; slot < limit; slot++) + if (!rvin_fill_hw_slot(vin, slot)) + return false; + return true; +} + +static void rvin_capture_on(struct rvin_dev *vin) +{ + vin_dbg(vin, "Capture on in %s mode\n", + vin->continuous ? "continuous" : "single"); + + if (vin->continuous) + /* Continuous Frame Capture Mode */ + rvin_write(vin, VNFC_C_FRAME, VNFC_REG); + else + /* Single Frame Capture Mode */ + rvin_write(vin, VNFC_S_FRAME, VNFC_REG); +} + +static int rvin_capture_start(struct rvin_dev *vin) +{ + struct rvin_buffer *buf, *node; + int bufs, ret; + + /* Count number of free buffers */ + bufs = 0; + list_for_each_entry_safe(buf, node, &vin->buf_list, list) + bufs++; + + /* Continuous capture requires more buffers then there are HW slots */ + vin->continuous = bufs > HW_BUFFER_NUM; + + if (!rvin_fill_hw(vin)) { + vin_err(vin, "HW not ready to start, not enough buffers available\n"); + return -EINVAL; + } + + rvin_crop_scale_comp(vin); + + ret = rvin_setup(vin); + if (ret) + return ret; + + rvin_capture_on(vin); + + vin->state = RUNNING; + + return 0; +} + +static void rvin_capture_stop(struct rvin_dev *vin) +{ + /* Set continuous & single transfer off */ + rvin_write(vin, 0, VNFC_REG); + + /* Disable module */ + rvin_write(vin, rvin_read(vin, VNMC_REG) & ~VNMC_ME, VNMC_REG); +} + /* ----------------------------------------------------------------------------- * Crop and Scaling Gen2 */ @@ -839,61 +899,12 @@ void rvin_scale_try(struct rvin_dev *vin, struct v4l2_pix_format *pix, #define RVIN_TIMEOUT_MS 100 #define RVIN_RETRIES 10 -struct rvin_buffer { - struct vb2_v4l2_buffer vb; - struct list_head list; -}; - -#define to_buf_list(vb2_buffer) (&container_of(vb2_buffer, \ - struct rvin_buffer, \ - vb)->list) - -/* Moves a buffer from the queue to the HW slots */ -static bool rvin_fill_hw_slot(struct rvin_dev *vin, int slot) -{ - struct rvin_buffer *buf; - struct vb2_v4l2_buffer *vbuf; - dma_addr_t phys_addr_top; - - if (vin->queue_buf[slot] != NULL) - return true; - - if (list_empty(&vin->buf_list)) - return false; - - vin_dbg(vin, "Filling HW slot: %d\n", slot); - - /* Keep track of buffer we give to HW */ - buf = list_entry(vin->buf_list.next, struct rvin_buffer, list); - vbuf = &buf->vb; - list_del_init(to_buf_list(vbuf)); - vin->queue_buf[slot] = vbuf; - - /* Setup DMA */ - phys_addr_top = vb2_dma_contig_plane_dma_addr(&vbuf->vb2_buf, 0); - rvin_set_slot_addr(vin, slot, phys_addr_top); - - return true; -} - -static bool rvin_fill_hw(struct rvin_dev *vin) -{ - int slot, limit; - - limit = vin->continuous ? HW_BUFFER_NUM : 1; - - for (slot = 0; slot < limit; slot++) - if (!rvin_fill_hw_slot(vin, slot)) - return false; - return true; -} - static irqreturn_t rvin_irq(int irq, void *data) { struct rvin_dev *vin = data; u32 int_status, vnms; int slot; - unsigned int sequence, handled = 0; + unsigned int i, sequence, handled = 0; unsigned long flags; spin_lock_irqsave(&vin->qlock, flags); @@ -955,8 +966,20 @@ static irqreturn_t rvin_irq(int irq, void *data) * the VnMBm registers. */ if (vin->continuous) { - rvin_capture_off(vin); + rvin_capture_stop(vin); vin_dbg(vin, "IRQ %02d: hw not ready stop\n", sequence); + + /* Maybe we can continue in single capture mode */ + for (i = 0; i < HW_BUFFER_NUM; i++) { + if (vin->queue_buf[i]) { + list_add(to_buf_list(vin->queue_buf[i]), + &vin->buf_list); + vin->queue_buf[i] = NULL; + } + } + + if (!list_empty(&vin->buf_list)) + rvin_capture_start(vin); } } else { /* @@ -1041,8 +1064,7 @@ static void rvin_buffer_queue(struct vb2_buffer *vb) * capturing if HW is ready to continue. */ if (vin->state == STALLED) - if (rvin_fill_hw(vin)) - rvin_capture_on(vin); + rvin_capture_start(vin); spin_unlock_irqrestore(&vin->qlock, flags); } @@ -1059,25 +1081,9 @@ static int rvin_start_streaming(struct vb2_queue *vq, unsigned int count) spin_lock_irqsave(&vin->qlock, flags); - vin->state = RUNNING; vin->sequence = 0; - /* Continuous capture requires more buffers then there are HW slots */ - vin->continuous = count > HW_BUFFER_NUM; - - /* - * This should never happen but if we don't have enough - * buffers for HW bail out - */ - if (!rvin_fill_hw(vin)) { - vin_err(vin, "HW not ready to start, not enough buffers available\n"); - ret = -EINVAL; - goto out; - } - ret = rvin_capture_start(vin); -out: - /* Return all buffers if something went wrong */ if (ret) { return_all_buffers(vin, VB2_BUF_STATE_QUEUED); v4l2_subdev_call(sd, video, s_stream, 0); @@ -1183,7 +1189,7 @@ int rvin_dma_probe(struct rvin_dev *vin, int irq) q->ops = &rvin_qops; q->mem_ops = &vb2_dma_contig_memops; q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; - q->min_buffers_needed = 2; + q->min_buffers_needed = 1; q->dev = vin->dev; ret = vb2_queue_init(q); diff --git a/drivers/media/platform/rcar-vin/rcar-v4l2.c b/drivers/media/platform/rcar-vin/rcar-v4l2.c index 2bbe6d495fa6..dd37ea811680 100644 --- a/drivers/media/platform/rcar-vin/rcar-v4l2.c +++ b/drivers/media/platform/rcar-vin/rcar-v4l2.c @@ -111,7 +111,7 @@ static int rvin_reset_format(struct rvin_dev *vin) struct v4l2_mbus_framefmt *mf = &fmt.format; int ret; - fmt.pad = vin->src_pad_idx; + fmt.pad = vin->digital.source_pad; ret = v4l2_subdev_call(vin_to_source(vin), pad, get_fmt, NULL, &fmt); if (ret) @@ -151,6 +151,9 @@ static int rvin_reset_format(struct rvin_dev *vin) rvin_reset_crop_compose(vin); + vin->format.bytesperline = rvin_format_bytesperline(&vin->format); + vin->format.sizeimage = rvin_format_sizeimage(&vin->format); + return 0; } @@ -175,7 +178,7 @@ static int __rvin_try_format_source(struct rvin_dev *vin, if (pad_cfg == NULL) return -ENOMEM; - format.pad = vin->src_pad_idx; + format.pad = vin->digital.source_pad; field = pix->field; @@ -203,8 +206,8 @@ static int __rvin_try_format(struct rvin_dev *vin, struct v4l2_pix_format *pix, struct rvin_source_fmt *source) { - const struct rvin_video_format *info; u32 rwidth, rheight, walign; + int ret; /* Requested */ rwidth = pix->width; @@ -214,17 +217,11 @@ static int __rvin_try_format(struct rvin_dev *vin, if (pix->field == V4L2_FIELD_ANY) pix->field = vin->format.field; - /* - * Retrieve format information and select the current format if the - * requested format isn't supported. - */ - info = rvin_format_from_pixel(pix->pixelformat); - if (!info) { - vin_dbg(vin, "Format %x not found, keeping %x\n", - pix->pixelformat, vin->format.pixelformat); - *pix = vin->format; - pix->width = rwidth; - pix->height = rheight; + /* If requested format is not supported fallback to the default */ + if (!rvin_format_from_pixel(pix->pixelformat)) { + vin_dbg(vin, "Format 0x%x not found, using default 0x%x\n", + pix->pixelformat, RVIN_DEFAULT_FORMAT); + pix->pixelformat = RVIN_DEFAULT_FORMAT; } /* Always recalculate */ @@ -232,7 +229,9 @@ static int __rvin_try_format(struct rvin_dev *vin, pix->sizeimage = 0; /* Limit to source capabilities */ - __rvin_try_format_source(vin, which, pix, source); + ret = __rvin_try_format_source(vin, which, pix, source); + if (ret) + return ret; switch (pix->field) { case V4L2_FIELD_TOP: @@ -480,10 +479,14 @@ static int rvin_enum_input(struct file *file, void *priv, return ret; i->type = V4L2_INPUT_TYPE_CAMERA; - i->std = vin->vdev.tvnorms; - if (v4l2_subdev_has_op(sd, pad, dv_timings_cap)) + if (v4l2_subdev_has_op(sd, pad, dv_timings_cap)) { i->capabilities = V4L2_IN_CAP_DV_TIMINGS; + i->std = 0; + } else { + i->capabilities = V4L2_IN_CAP_STD; + i->std = vin->vdev.tvnorms; + } strlcpy(i->name, "Camera", sizeof(i->name)); @@ -547,14 +550,16 @@ static int rvin_enum_dv_timings(struct file *file, void *priv_fh, { struct rvin_dev *vin = video_drvdata(file); struct v4l2_subdev *sd = vin_to_source(vin); - int pad, ret; + int ret; + + if (timings->pad) + return -EINVAL; - pad = timings->pad; - timings->pad = vin->sink_pad_idx; + timings->pad = vin->digital.sink_pad; ret = v4l2_subdev_call(sd, pad, enum_dv_timings, timings); - timings->pad = pad; + timings->pad = 0; return ret; } @@ -570,12 +575,8 @@ static int rvin_s_dv_timings(struct file *file, void *priv_fh, if (ret) return ret; - vin->source.width = timings->bt.width; - vin->source.height = timings->bt.height; - vin->format.width = timings->bt.width; - vin->format.height = timings->bt.height; - - return 0; + /* Changing the timings will change the width/height */ + return rvin_reset_format(vin); } static int rvin_g_dv_timings(struct file *file, void *priv_fh, @@ -601,14 +602,16 @@ static int rvin_dv_timings_cap(struct file *file, void *priv_fh, { struct rvin_dev *vin = video_drvdata(file); struct v4l2_subdev *sd = vin_to_source(vin); - int pad, ret; + int ret; + + if (cap->pad) + return -EINVAL; - pad = cap->pad; - cap->pad = vin->sink_pad_idx; + cap->pad = vin->digital.sink_pad; ret = v4l2_subdev_call(sd, pad, dv_timings_cap, cap); - cap->pad = pad; + cap->pad = 0; return ret; } @@ -617,17 +620,16 @@ static int rvin_g_edid(struct file *file, void *fh, struct v4l2_edid *edid) { struct rvin_dev *vin = video_drvdata(file); struct v4l2_subdev *sd = vin_to_source(vin); - int input, ret; + int ret; if (edid->pad) return -EINVAL; - input = edid->pad; - edid->pad = vin->sink_pad_idx; + edid->pad = vin->digital.sink_pad; ret = v4l2_subdev_call(sd, pad, get_edid, edid); - edid->pad = input; + edid->pad = 0; return ret; } @@ -636,17 +638,16 @@ static int rvin_s_edid(struct file *file, void *fh, struct v4l2_edid *edid) { struct rvin_dev *vin = video_drvdata(file); struct v4l2_subdev *sd = vin_to_source(vin); - int input, ret; + int ret; if (edid->pad) return -EINVAL; - input = edid->pad; - edid->pad = vin->sink_pad_idx; + edid->pad = vin->digital.sink_pad; ret = v4l2_subdev_call(sd, pad, set_edid, edid); - edid->pad = input; + edid->pad = 0; return ret; } @@ -869,7 +870,7 @@ int rvin_v4l2_probe(struct rvin_dev *vin) { struct video_device *vdev = &vin->vdev; struct v4l2_subdev *sd = vin_to_source(vin); - int pad_idx, ret; + int ret; v4l2_set_subdev_hostdata(sd, vin); @@ -915,22 +916,6 @@ int rvin_v4l2_probe(struct rvin_dev *vin) vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | V4L2_CAP_READWRITE; - vin->src_pad_idx = 0; - for (pad_idx = 0; pad_idx < sd->entity.num_pads; pad_idx++) - if (sd->entity.pads[pad_idx].flags == MEDIA_PAD_FL_SOURCE) - break; - if (pad_idx >= sd->entity.num_pads) - return -EINVAL; - - vin->src_pad_idx = pad_idx; - - vin->sink_pad_idx = 0; - for (pad_idx = 0; pad_idx < sd->entity.num_pads; pad_idx++) - if (sd->entity.pads[pad_idx].flags == MEDIA_PAD_FL_SINK) { - vin->sink_pad_idx = pad_idx; - break; - } - vin->format.pixelformat = RVIN_DEFAULT_FORMAT; rvin_reset_format(vin); diff --git a/drivers/media/platform/rcar-vin/rcar-vin.h b/drivers/media/platform/rcar-vin/rcar-vin.h index 727e215c0871..9bfb5a7c4dc4 100644 --- a/drivers/media/platform/rcar-vin/rcar-vin.h +++ b/drivers/media/platform/rcar-vin/rcar-vin.h @@ -74,6 +74,8 @@ struct rvin_video_format { * @subdev: subdevice matched using async framework * @code: Media bus format from source * @mbus_cfg: Media bus format from DT + * @source_pad: source pad of remote subdevice + * @sink_pad: sink pad of remote subdevice */ struct rvin_graph_entity { struct v4l2_async_subdev asd; @@ -81,6 +83,9 @@ struct rvin_graph_entity { u32 code; struct v4l2_mbus_config mbus_cfg; + + unsigned int source_pad; + unsigned int sink_pad; }; /** @@ -91,8 +96,6 @@ struct rvin_graph_entity { * * @vdev: V4L2 video device associated with VIN * @v4l2_dev: V4L2 device - * @src_pad_idx: source pad index for media controller drivers - * @sink_pad_idx: sink pad index for media controller drivers * @ctrl_handler: V4L2 control handler * @notifier: V4L2 asynchronous subdevs notifier * @digital: entity in the DT for local digital subdevice @@ -121,8 +124,6 @@ struct rvin_dev { struct video_device vdev; struct v4l2_device v4l2_dev; - int src_pad_idx; - int sink_pad_idx; struct v4l2_ctrl_handler ctrl_handler; struct v4l2_async_notifier notifier; struct rvin_graph_entity digital; diff --git a/drivers/media/platform/rcar_drif.c b/drivers/media/platform/rcar_drif.c new file mode 100644 index 000000000000..522364ff0d5d --- /dev/null +++ b/drivers/media/platform/rcar_drif.c @@ -0,0 +1,1498 @@ +/* + * R-Car Gen3 Digital Radio Interface (DRIF) driver + * + * Copyright (C) 2017 Renesas Electronics Corporation + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* + * The R-Car DRIF is a receive only MSIOF like controller with an + * external master device driving the SCK. It receives data into a FIFO, + * then this driver uses the SYS-DMAC engine to move the data from + * the device to memory. + * + * Each DRIF channel DRIFx (as per datasheet) contains two internal + * channels DRIFx0 & DRIFx1 within itself with each having its own resources + * like module clk, register set, irq and dma. These internal channels share + * common CLK & SYNC from master. The two data pins D0 & D1 shall be + * considered to represent the two internal channels. This internal split + * is not visible to the master device. + * + * Depending on the master device, a DRIF channel can use + * (1) both internal channels (D0 & D1) to receive data in parallel (or) + * (2) one internal channel (D0 or D1) to receive data + * + * The primary design goal of this controller is to act as a Digital Radio + * Interface that receives digital samples from a tuner device. Hence the + * driver exposes the device as a V4L2 SDR device. In order to qualify as + * a V4L2 SDR device, it should possess a tuner interface as mandated by the + * framework. This driver expects a tuner driver (sub-device) to bind + * asynchronously with this device and the combined drivers shall expose + * a V4L2 compliant SDR device. The DRIF driver is independent of the + * tuner vendor. + * + * The DRIF h/w can support I2S mode and Frame start synchronization pulse mode. + * This driver is tested for I2S mode only because of the availability of + * suitable master devices. Hence, not all configurable options of DRIF h/w + * like lsb/msb first, syncdl, dtdl etc. are exposed via DT and I2S defaults + * are used. These can be exposed later if needed after testing. + */ +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/dma-mapping.h> +#include <linux/dmaengine.h> +#include <linux/ioctl.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/of_graph.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/sched.h> +#include <media/v4l2-async.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-fh.h> +#include <media/v4l2-ioctl.h> +#include <media/videobuf2-v4l2.h> +#include <media/videobuf2-vmalloc.h> + +/* DRIF register offsets */ +#define RCAR_DRIF_SITMDR1 0x00 +#define RCAR_DRIF_SITMDR2 0x04 +#define RCAR_DRIF_SITMDR3 0x08 +#define RCAR_DRIF_SIRMDR1 0x10 +#define RCAR_DRIF_SIRMDR2 0x14 +#define RCAR_DRIF_SIRMDR3 0x18 +#define RCAR_DRIF_SICTR 0x28 +#define RCAR_DRIF_SIFCTR 0x30 +#define RCAR_DRIF_SISTR 0x40 +#define RCAR_DRIF_SIIER 0x44 +#define RCAR_DRIF_SIRFDR 0x60 + +#define RCAR_DRIF_RFOVF BIT(3) /* Receive FIFO overflow */ +#define RCAR_DRIF_RFUDF BIT(4) /* Receive FIFO underflow */ +#define RCAR_DRIF_RFSERR BIT(5) /* Receive frame sync error */ +#define RCAR_DRIF_REOF BIT(7) /* Frame reception end */ +#define RCAR_DRIF_RDREQ BIT(12) /* Receive data xfer req */ +#define RCAR_DRIF_RFFUL BIT(13) /* Receive FIFO full */ + +/* SIRMDR1 */ +#define RCAR_DRIF_SIRMDR1_SYNCMD_FRAME (0 << 28) +#define RCAR_DRIF_SIRMDR1_SYNCMD_LR (3 << 28) + +#define RCAR_DRIF_SIRMDR1_SYNCAC_POL_HIGH (0 << 25) +#define RCAR_DRIF_SIRMDR1_SYNCAC_POL_LOW (1 << 25) + +#define RCAR_DRIF_SIRMDR1_MSB_FIRST (0 << 24) +#define RCAR_DRIF_SIRMDR1_LSB_FIRST (1 << 24) + +#define RCAR_DRIF_SIRMDR1_DTDL_0 (0 << 20) +#define RCAR_DRIF_SIRMDR1_DTDL_1 (1 << 20) +#define RCAR_DRIF_SIRMDR1_DTDL_2 (2 << 20) +#define RCAR_DRIF_SIRMDR1_DTDL_0PT5 (5 << 20) +#define RCAR_DRIF_SIRMDR1_DTDL_1PT5 (6 << 20) + +#define RCAR_DRIF_SIRMDR1_SYNCDL_0 (0 << 20) +#define RCAR_DRIF_SIRMDR1_SYNCDL_1 (1 << 20) +#define RCAR_DRIF_SIRMDR1_SYNCDL_2 (2 << 20) +#define RCAR_DRIF_SIRMDR1_SYNCDL_3 (3 << 20) +#define RCAR_DRIF_SIRMDR1_SYNCDL_0PT5 (5 << 20) +#define RCAR_DRIF_SIRMDR1_SYNCDL_1PT5 (6 << 20) + +#define RCAR_DRIF_MDR_GRPCNT(n) (((n) - 1) << 30) +#define RCAR_DRIF_MDR_BITLEN(n) (((n) - 1) << 24) +#define RCAR_DRIF_MDR_WDCNT(n) (((n) - 1) << 16) + +/* Hidden Transmit register that controls CLK & SYNC */ +#define RCAR_DRIF_SITMDR1_PCON BIT(30) + +#define RCAR_DRIF_SICTR_RX_RISING_EDGE BIT(26) +#define RCAR_DRIF_SICTR_RX_EN BIT(8) +#define RCAR_DRIF_SICTR_RESET BIT(0) + +/* Constants */ +#define RCAR_DRIF_NUM_HWBUFS 32 +#define RCAR_DRIF_MAX_DEVS 4 +#define RCAR_DRIF_DEFAULT_NUM_HWBUFS 16 +#define RCAR_DRIF_DEFAULT_HWBUF_SIZE (4 * PAGE_SIZE) +#define RCAR_DRIF_MAX_CHANNEL 2 +#define RCAR_SDR_BUFFER_SIZE SZ_64K + +/* Internal buffer status flags */ +#define RCAR_DRIF_BUF_DONE BIT(0) /* DMA completed */ +#define RCAR_DRIF_BUF_OVERFLOW BIT(1) /* Overflow detected */ + +#define to_rcar_drif_buf_pair(sdr, ch_num, idx) \ + (&((sdr)->ch[!(ch_num)]->buf[(idx)])) + +#define for_each_rcar_drif_channel(ch, ch_mask) \ + for_each_set_bit(ch, ch_mask, RCAR_DRIF_MAX_CHANNEL) + +/* Debug */ +#define rdrif_dbg(sdr, fmt, arg...) \ + dev_dbg(sdr->v4l2_dev.dev, fmt, ## arg) + +#define rdrif_err(sdr, fmt, arg...) \ + dev_err(sdr->v4l2_dev.dev, fmt, ## arg) + +/* Stream formats */ +struct rcar_drif_format { + u32 pixelformat; + u32 buffersize; + u32 bitlen; + u32 wdcnt; + u32 num_ch; +}; + +/* Format descriptions for capture */ +static const struct rcar_drif_format formats[] = { + { + .pixelformat = V4L2_SDR_FMT_PCU16BE, + .buffersize = RCAR_SDR_BUFFER_SIZE, + .bitlen = 16, + .wdcnt = 1, + .num_ch = 2, + }, + { + .pixelformat = V4L2_SDR_FMT_PCU18BE, + .buffersize = RCAR_SDR_BUFFER_SIZE, + .bitlen = 18, + .wdcnt = 1, + .num_ch = 2, + }, + { + .pixelformat = V4L2_SDR_FMT_PCU20BE, + .buffersize = RCAR_SDR_BUFFER_SIZE, + .bitlen = 20, + .wdcnt = 1, + .num_ch = 2, + }, +}; + +/* Buffer for a received frame from one or both internal channels */ +struct rcar_drif_frame_buf { + /* Common v4l buffer stuff -- must be first */ + struct vb2_v4l2_buffer vb; + struct list_head list; +}; + +/* OF graph endpoint's V4L2 async data */ +struct rcar_drif_graph_ep { + struct v4l2_subdev *subdev; /* Async matched subdev */ + struct v4l2_async_subdev asd; /* Async sub-device descriptor */ +}; + +/* DMA buffer */ +struct rcar_drif_hwbuf { + void *addr; /* CPU-side address */ + unsigned int status; /* Buffer status flags */ +}; + +/* Internal channel */ +struct rcar_drif { + struct rcar_drif_sdr *sdr; /* Group device */ + struct platform_device *pdev; /* Channel's pdev */ + void __iomem *base; /* Base register address */ + resource_size_t start; /* I/O resource offset */ + struct dma_chan *dmach; /* Reserved DMA channel */ + struct clk *clk; /* Module clock */ + struct rcar_drif_hwbuf buf[RCAR_DRIF_NUM_HWBUFS]; /* H/W bufs */ + dma_addr_t dma_handle; /* Handle for all bufs */ + unsigned int num; /* Channel number */ + bool acting_sdr; /* Channel acting as SDR device */ +}; + +/* DRIF V4L2 SDR */ +struct rcar_drif_sdr { + struct device *dev; /* Platform device */ + struct video_device *vdev; /* V4L2 SDR device */ + struct v4l2_device v4l2_dev; /* V4L2 device */ + + /* Videobuf2 queue and queued buffers list */ + struct vb2_queue vb_queue; + struct list_head queued_bufs; + spinlock_t queued_bufs_lock; /* Protects queued_bufs */ + spinlock_t dma_lock; /* To serialize DMA cb of channels */ + + struct mutex v4l2_mutex; /* To serialize ioctls */ + struct mutex vb_queue_mutex; /* To serialize streaming ioctls */ + struct v4l2_ctrl_handler ctrl_hdl; /* SDR control handler */ + struct v4l2_async_notifier notifier; /* For subdev (tuner) */ + struct rcar_drif_graph_ep ep; /* Endpoint V4L2 async data */ + + /* Current V4L2 SDR format ptr */ + const struct rcar_drif_format *fmt; + + /* Device tree SYNC properties */ + u32 mdr1; + + /* Internals */ + struct rcar_drif *ch[RCAR_DRIF_MAX_CHANNEL]; /* DRIFx0,1 */ + unsigned long hw_ch_mask; /* Enabled channels per DT */ + unsigned long cur_ch_mask; /* Used channels for an SDR FMT */ + u32 num_hw_ch; /* Num of DT enabled channels */ + u32 num_cur_ch; /* Num of used channels */ + u32 hwbuf_size; /* Each DMA buffer size */ + u32 produced; /* Buffers produced by sdr dev */ +}; + +/* Register access functions */ +static void rcar_drif_write(struct rcar_drif *ch, u32 offset, u32 data) +{ + writel(data, ch->base + offset); +} + +static u32 rcar_drif_read(struct rcar_drif *ch, u32 offset) +{ + return readl(ch->base + offset); +} + +/* Release DMA channels */ +static void rcar_drif_release_dmachannels(struct rcar_drif_sdr *sdr) +{ + unsigned int i; + + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) + if (sdr->ch[i]->dmach) { + dma_release_channel(sdr->ch[i]->dmach); + sdr->ch[i]->dmach = NULL; + } +} + +/* Allocate DMA channels */ +static int rcar_drif_alloc_dmachannels(struct rcar_drif_sdr *sdr) +{ + struct dma_slave_config dma_cfg; + unsigned int i; + int ret = -ENODEV; + + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) { + struct rcar_drif *ch = sdr->ch[i]; + + ch->dmach = dma_request_slave_channel(&ch->pdev->dev, "rx"); + if (!ch->dmach) { + rdrif_err(sdr, "ch%u: dma channel req failed\n", i); + goto dmach_error; + } + + /* Configure slave */ + memset(&dma_cfg, 0, sizeof(dma_cfg)); + dma_cfg.src_addr = (phys_addr_t)(ch->start + RCAR_DRIF_SIRFDR); + dma_cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + ret = dmaengine_slave_config(ch->dmach, &dma_cfg); + if (ret) { + rdrif_err(sdr, "ch%u: dma slave config failed\n", i); + goto dmach_error; + } + } + return 0; + +dmach_error: + rcar_drif_release_dmachannels(sdr); + return ret; +} + +/* Release queued vb2 buffers */ +static void rcar_drif_release_queued_bufs(struct rcar_drif_sdr *sdr, + enum vb2_buffer_state state) +{ + struct rcar_drif_frame_buf *fbuf, *tmp; + unsigned long flags; + + spin_lock_irqsave(&sdr->queued_bufs_lock, flags); + list_for_each_entry_safe(fbuf, tmp, &sdr->queued_bufs, list) { + list_del(&fbuf->list); + vb2_buffer_done(&fbuf->vb.vb2_buf, state); + } + spin_unlock_irqrestore(&sdr->queued_bufs_lock, flags); +} + +/* Set MDR defaults */ +static inline void rcar_drif_set_mdr1(struct rcar_drif_sdr *sdr) +{ + unsigned int i; + + /* Set defaults for enabled internal channels */ + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) { + /* Refer MSIOF section in manual for this register setting */ + rcar_drif_write(sdr->ch[i], RCAR_DRIF_SITMDR1, + RCAR_DRIF_SITMDR1_PCON); + + /* Setup MDR1 value */ + rcar_drif_write(sdr->ch[i], RCAR_DRIF_SIRMDR1, sdr->mdr1); + + rdrif_dbg(sdr, "ch%u: mdr1 = 0x%08x", + i, rcar_drif_read(sdr->ch[i], RCAR_DRIF_SIRMDR1)); + } +} + +/* Set DRIF receive format */ +static int rcar_drif_set_format(struct rcar_drif_sdr *sdr) +{ + unsigned int i; + + rdrif_dbg(sdr, "setfmt: bitlen %u wdcnt %u num_ch %u\n", + sdr->fmt->bitlen, sdr->fmt->wdcnt, sdr->fmt->num_ch); + + /* Sanity check */ + if (sdr->fmt->num_ch > sdr->num_cur_ch) { + rdrif_err(sdr, "fmt num_ch %u cur_ch %u mismatch\n", + sdr->fmt->num_ch, sdr->num_cur_ch); + return -EINVAL; + } + + /* Setup group, bitlen & wdcnt */ + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) { + u32 mdr; + + /* Two groups */ + mdr = RCAR_DRIF_MDR_GRPCNT(2) | + RCAR_DRIF_MDR_BITLEN(sdr->fmt->bitlen) | + RCAR_DRIF_MDR_WDCNT(sdr->fmt->wdcnt); + rcar_drif_write(sdr->ch[i], RCAR_DRIF_SIRMDR2, mdr); + + mdr = RCAR_DRIF_MDR_BITLEN(sdr->fmt->bitlen) | + RCAR_DRIF_MDR_WDCNT(sdr->fmt->wdcnt); + rcar_drif_write(sdr->ch[i], RCAR_DRIF_SIRMDR3, mdr); + + rdrif_dbg(sdr, "ch%u: new mdr[2,3] = 0x%08x, 0x%08x\n", + i, rcar_drif_read(sdr->ch[i], RCAR_DRIF_SIRMDR2), + rcar_drif_read(sdr->ch[i], RCAR_DRIF_SIRMDR3)); + } + return 0; +} + +/* Release DMA buffers */ +static void rcar_drif_release_buf(struct rcar_drif_sdr *sdr) +{ + unsigned int i; + + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) { + struct rcar_drif *ch = sdr->ch[i]; + + /* First entry contains the dma buf ptr */ + if (ch->buf[0].addr) { + dma_free_coherent(&ch->pdev->dev, + sdr->hwbuf_size * RCAR_DRIF_NUM_HWBUFS, + ch->buf[0].addr, ch->dma_handle); + ch->buf[0].addr = NULL; + } + } +} + +/* Request DMA buffers */ +static int rcar_drif_request_buf(struct rcar_drif_sdr *sdr) +{ + int ret = -ENOMEM; + unsigned int i, j; + void *addr; + + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) { + struct rcar_drif *ch = sdr->ch[i]; + + /* Allocate DMA buffers */ + addr = dma_alloc_coherent(&ch->pdev->dev, + sdr->hwbuf_size * RCAR_DRIF_NUM_HWBUFS, + &ch->dma_handle, GFP_KERNEL); + if (!addr) { + rdrif_err(sdr, + "ch%u: dma alloc failed. num hwbufs %u size %u\n", + i, RCAR_DRIF_NUM_HWBUFS, sdr->hwbuf_size); + goto error; + } + + /* Split the chunk and populate bufctxt */ + for (j = 0; j < RCAR_DRIF_NUM_HWBUFS; j++) { + ch->buf[j].addr = addr + (j * sdr->hwbuf_size); + ch->buf[j].status = 0; + } + } + return 0; +error: + return ret; +} + +/* Setup vb_queue minimum buffer requirements */ +static int rcar_drif_queue_setup(struct vb2_queue *vq, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct rcar_drif_sdr *sdr = vb2_get_drv_priv(vq); + + /* Need at least 16 buffers */ + if (vq->num_buffers + *num_buffers < 16) + *num_buffers = 16 - vq->num_buffers; + + *num_planes = 1; + sizes[0] = PAGE_ALIGN(sdr->fmt->buffersize); + rdrif_dbg(sdr, "num_bufs %d sizes[0] %d\n", *num_buffers, sizes[0]); + + return 0; +} + +/* Enqueue buffer */ +static void rcar_drif_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct rcar_drif_sdr *sdr = vb2_get_drv_priv(vb->vb2_queue); + struct rcar_drif_frame_buf *fbuf = + container_of(vbuf, struct rcar_drif_frame_buf, vb); + unsigned long flags; + + rdrif_dbg(sdr, "buf_queue idx %u\n", vb->index); + spin_lock_irqsave(&sdr->queued_bufs_lock, flags); + list_add_tail(&fbuf->list, &sdr->queued_bufs); + spin_unlock_irqrestore(&sdr->queued_bufs_lock, flags); +} + +/* Get a frame buf from list */ +static struct rcar_drif_frame_buf * +rcar_drif_get_fbuf(struct rcar_drif_sdr *sdr) +{ + struct rcar_drif_frame_buf *fbuf; + unsigned long flags; + + spin_lock_irqsave(&sdr->queued_bufs_lock, flags); + fbuf = list_first_entry_or_null(&sdr->queued_bufs, struct + rcar_drif_frame_buf, list); + if (!fbuf) { + /* + * App is late in enqueing buffers. Samples lost & there will + * be a gap in sequence number when app recovers + */ + rdrif_dbg(sdr, "\napp late: prod %u\n", sdr->produced); + spin_unlock_irqrestore(&sdr->queued_bufs_lock, flags); + return NULL; + } + list_del(&fbuf->list); + spin_unlock_irqrestore(&sdr->queued_bufs_lock, flags); + + return fbuf; +} + +/* Helpers to set/clear buf pair status */ +static inline bool rcar_drif_bufs_done(struct rcar_drif_hwbuf **buf) +{ + return (buf[0]->status & buf[1]->status & RCAR_DRIF_BUF_DONE); +} + +static inline bool rcar_drif_bufs_overflow(struct rcar_drif_hwbuf **buf) +{ + return ((buf[0]->status | buf[1]->status) & RCAR_DRIF_BUF_OVERFLOW); +} + +static inline void rcar_drif_bufs_clear(struct rcar_drif_hwbuf **buf, + unsigned int bit) +{ + unsigned int i; + + for (i = 0; i < RCAR_DRIF_MAX_CHANNEL; i++) + buf[i]->status &= ~bit; +} + +/* Channel DMA complete */ +static void rcar_drif_channel_complete(struct rcar_drif *ch, u32 idx) +{ + u32 str; + + ch->buf[idx].status |= RCAR_DRIF_BUF_DONE; + + /* Check for DRIF errors */ + str = rcar_drif_read(ch, RCAR_DRIF_SISTR); + if (unlikely(str & RCAR_DRIF_RFOVF)) { + /* Writing the same clears it */ + rcar_drif_write(ch, RCAR_DRIF_SISTR, str); + + /* Overflow: some samples are lost */ + ch->buf[idx].status |= RCAR_DRIF_BUF_OVERFLOW; + } +} + +/* DMA callback for each stage */ +static void rcar_drif_dma_complete(void *dma_async_param) +{ + struct rcar_drif *ch = dma_async_param; + struct rcar_drif_sdr *sdr = ch->sdr; + struct rcar_drif_hwbuf *buf[RCAR_DRIF_MAX_CHANNEL]; + struct rcar_drif_frame_buf *fbuf; + bool overflow = false; + u32 idx, produced; + unsigned int i; + + spin_lock(&sdr->dma_lock); + + /* DMA can be terminated while the callback was waiting on lock */ + if (!vb2_is_streaming(&sdr->vb_queue)) { + spin_unlock(&sdr->dma_lock); + return; + } + + idx = sdr->produced % RCAR_DRIF_NUM_HWBUFS; + rcar_drif_channel_complete(ch, idx); + + if (sdr->num_cur_ch == RCAR_DRIF_MAX_CHANNEL) { + buf[0] = ch->num ? to_rcar_drif_buf_pair(sdr, ch->num, idx) : + &ch->buf[idx]; + buf[1] = ch->num ? &ch->buf[idx] : + to_rcar_drif_buf_pair(sdr, ch->num, idx); + + /* Check if both DMA buffers are done */ + if (!rcar_drif_bufs_done(buf)) { + spin_unlock(&sdr->dma_lock); + return; + } + + /* Clear buf done status */ + rcar_drif_bufs_clear(buf, RCAR_DRIF_BUF_DONE); + + if (rcar_drif_bufs_overflow(buf)) { + overflow = true; + /* Clear the flag in status */ + rcar_drif_bufs_clear(buf, RCAR_DRIF_BUF_OVERFLOW); + } + } else { + buf[0] = &ch->buf[idx]; + if (buf[0]->status & RCAR_DRIF_BUF_OVERFLOW) { + overflow = true; + /* Clear the flag in status */ + buf[0]->status &= ~RCAR_DRIF_BUF_OVERFLOW; + } + } + + /* Buffer produced for consumption */ + produced = sdr->produced++; + spin_unlock(&sdr->dma_lock); + + rdrif_dbg(sdr, "ch%u: prod %u\n", ch->num, produced); + + /* Get fbuf */ + fbuf = rcar_drif_get_fbuf(sdr); + if (!fbuf) + return; + + for (i = 0; i < RCAR_DRIF_MAX_CHANNEL; i++) + memcpy(vb2_plane_vaddr(&fbuf->vb.vb2_buf, 0) + + i * sdr->hwbuf_size, buf[i]->addr, sdr->hwbuf_size); + + fbuf->vb.field = V4L2_FIELD_NONE; + fbuf->vb.sequence = produced; + fbuf->vb.vb2_buf.timestamp = ktime_get_ns(); + vb2_set_plane_payload(&fbuf->vb.vb2_buf, 0, sdr->fmt->buffersize); + + /* Set error state on overflow */ + vb2_buffer_done(&fbuf->vb.vb2_buf, + overflow ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE); +} + +static int rcar_drif_qbuf(struct rcar_drif *ch) +{ + struct rcar_drif_sdr *sdr = ch->sdr; + dma_addr_t addr = ch->dma_handle; + struct dma_async_tx_descriptor *rxd; + dma_cookie_t cookie; + int ret = -EIO; + + /* Setup cyclic DMA with given buffers */ + rxd = dmaengine_prep_dma_cyclic(ch->dmach, addr, + sdr->hwbuf_size * RCAR_DRIF_NUM_HWBUFS, + sdr->hwbuf_size, DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!rxd) { + rdrif_err(sdr, "ch%u: prep dma cyclic failed\n", ch->num); + return ret; + } + + /* Submit descriptor */ + rxd->callback = rcar_drif_dma_complete; + rxd->callback_param = ch; + cookie = dmaengine_submit(rxd); + if (dma_submit_error(cookie)) { + rdrif_err(sdr, "ch%u: dma submit failed\n", ch->num); + return ret; + } + + dma_async_issue_pending(ch->dmach); + return 0; +} + +/* Enable reception */ +static int rcar_drif_enable_rx(struct rcar_drif_sdr *sdr) +{ + unsigned int i; + u32 ctr; + int ret; + + /* + * When both internal channels are enabled, they can be synchronized + * only by the master + */ + + /* Enable receive */ + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) { + ctr = rcar_drif_read(sdr->ch[i], RCAR_DRIF_SICTR); + ctr |= (RCAR_DRIF_SICTR_RX_RISING_EDGE | + RCAR_DRIF_SICTR_RX_EN); + rcar_drif_write(sdr->ch[i], RCAR_DRIF_SICTR, ctr); + } + + /* Check receive enabled */ + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) { + ret = readl_poll_timeout(sdr->ch[i]->base + RCAR_DRIF_SICTR, + ctr, ctr & RCAR_DRIF_SICTR_RX_EN, 7, 100000); + if (ret) { + rdrif_err(sdr, "ch%u: rx en failed. ctr 0x%08x\n", i, + rcar_drif_read(sdr->ch[i], RCAR_DRIF_SICTR)); + break; + } + } + return ret; +} + +/* Disable reception */ +static void rcar_drif_disable_rx(struct rcar_drif_sdr *sdr) +{ + unsigned int i; + u32 ctr; + int ret; + + /* Disable receive */ + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) { + ctr = rcar_drif_read(sdr->ch[i], RCAR_DRIF_SICTR); + ctr &= ~RCAR_DRIF_SICTR_RX_EN; + rcar_drif_write(sdr->ch[i], RCAR_DRIF_SICTR, ctr); + } + + /* Check receive disabled */ + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) { + ret = readl_poll_timeout(sdr->ch[i]->base + RCAR_DRIF_SICTR, + ctr, !(ctr & RCAR_DRIF_SICTR_RX_EN), 7, 100000); + if (ret) + dev_warn(&sdr->vdev->dev, + "ch%u: failed to disable rx. ctr 0x%08x\n", + i, rcar_drif_read(sdr->ch[i], RCAR_DRIF_SICTR)); + } +} + +/* Stop channel */ +static void rcar_drif_stop_channel(struct rcar_drif *ch) +{ + /* Disable DMA receive interrupt */ + rcar_drif_write(ch, RCAR_DRIF_SIIER, 0x00000000); + + /* Terminate all DMA transfers */ + dmaengine_terminate_sync(ch->dmach); +} + +/* Stop receive operation */ +static void rcar_drif_stop(struct rcar_drif_sdr *sdr) +{ + unsigned int i; + + /* Disable Rx */ + rcar_drif_disable_rx(sdr); + + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) + rcar_drif_stop_channel(sdr->ch[i]); +} + +/* Start channel */ +static int rcar_drif_start_channel(struct rcar_drif *ch) +{ + struct rcar_drif_sdr *sdr = ch->sdr; + u32 ctr, str; + int ret; + + /* Reset receive */ + rcar_drif_write(ch, RCAR_DRIF_SICTR, RCAR_DRIF_SICTR_RESET); + ret = readl_poll_timeout(ch->base + RCAR_DRIF_SICTR, ctr, + !(ctr & RCAR_DRIF_SICTR_RESET), 7, 100000); + if (ret) { + rdrif_err(sdr, "ch%u: failed to reset rx. ctr 0x%08x\n", + ch->num, rcar_drif_read(ch, RCAR_DRIF_SICTR)); + return ret; + } + + /* Queue buffers for DMA */ + ret = rcar_drif_qbuf(ch); + if (ret) + return ret; + + /* Clear status register flags */ + str = RCAR_DRIF_RFFUL | RCAR_DRIF_REOF | RCAR_DRIF_RFSERR | + RCAR_DRIF_RFUDF | RCAR_DRIF_RFOVF; + rcar_drif_write(ch, RCAR_DRIF_SISTR, str); + + /* Enable DMA receive interrupt */ + rcar_drif_write(ch, RCAR_DRIF_SIIER, 0x00009000); + + return ret; +} + +/* Start receive operation */ +static int rcar_drif_start(struct rcar_drif_sdr *sdr) +{ + unsigned long enabled = 0; + unsigned int i; + int ret; + + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) { + ret = rcar_drif_start_channel(sdr->ch[i]); + if (ret) + goto start_error; + enabled |= BIT(i); + } + + ret = rcar_drif_enable_rx(sdr); + if (ret) + goto enable_error; + + sdr->produced = 0; + return ret; + +enable_error: + rcar_drif_disable_rx(sdr); +start_error: + for_each_rcar_drif_channel(i, &enabled) + rcar_drif_stop_channel(sdr->ch[i]); + + return ret; +} + +/* Start streaming */ +static int rcar_drif_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct rcar_drif_sdr *sdr = vb2_get_drv_priv(vq); + unsigned long enabled = 0; + unsigned int i; + int ret; + + mutex_lock(&sdr->v4l2_mutex); + + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) { + ret = clk_prepare_enable(sdr->ch[i]->clk); + if (ret) + goto error; + enabled |= BIT(i); + } + + /* Set default MDRx settings */ + rcar_drif_set_mdr1(sdr); + + /* Set new format */ + ret = rcar_drif_set_format(sdr); + if (ret) + goto error; + + if (sdr->num_cur_ch == RCAR_DRIF_MAX_CHANNEL) + sdr->hwbuf_size = sdr->fmt->buffersize / RCAR_DRIF_MAX_CHANNEL; + else + sdr->hwbuf_size = sdr->fmt->buffersize; + + rdrif_dbg(sdr, "num hwbufs %u, hwbuf_size %u\n", + RCAR_DRIF_NUM_HWBUFS, sdr->hwbuf_size); + + /* Alloc DMA channel */ + ret = rcar_drif_alloc_dmachannels(sdr); + if (ret) + goto error; + + /* Request buffers */ + ret = rcar_drif_request_buf(sdr); + if (ret) + goto error; + + /* Start Rx */ + ret = rcar_drif_start(sdr); + if (ret) + goto error; + + mutex_unlock(&sdr->v4l2_mutex); + + return ret; + +error: + rcar_drif_release_queued_bufs(sdr, VB2_BUF_STATE_QUEUED); + rcar_drif_release_buf(sdr); + rcar_drif_release_dmachannels(sdr); + for_each_rcar_drif_channel(i, &enabled) + clk_disable_unprepare(sdr->ch[i]->clk); + + mutex_unlock(&sdr->v4l2_mutex); + + return ret; +} + +/* Stop streaming */ +static void rcar_drif_stop_streaming(struct vb2_queue *vq) +{ + struct rcar_drif_sdr *sdr = vb2_get_drv_priv(vq); + unsigned int i; + + mutex_lock(&sdr->v4l2_mutex); + + /* Stop hardware streaming */ + rcar_drif_stop(sdr); + + /* Return all queued buffers to vb2 */ + rcar_drif_release_queued_bufs(sdr, VB2_BUF_STATE_ERROR); + + /* Release buf */ + rcar_drif_release_buf(sdr); + + /* Release DMA channel resources */ + rcar_drif_release_dmachannels(sdr); + + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) + clk_disable_unprepare(sdr->ch[i]->clk); + + mutex_unlock(&sdr->v4l2_mutex); +} + +/* Vb2 ops */ +static const struct vb2_ops rcar_drif_vb2_ops = { + .queue_setup = rcar_drif_queue_setup, + .buf_queue = rcar_drif_buf_queue, + .start_streaming = rcar_drif_start_streaming, + .stop_streaming = rcar_drif_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int rcar_drif_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + struct rcar_drif_sdr *sdr = video_drvdata(file); + + strlcpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver)); + strlcpy(cap->card, sdr->vdev->name, sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", + sdr->vdev->name); + + return 0; +} + +static int rcar_drif_set_default_format(struct rcar_drif_sdr *sdr) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(formats); i++) { + /* Matching fmt based on required channels is set as default */ + if (sdr->num_hw_ch == formats[i].num_ch) { + sdr->fmt = &formats[i]; + sdr->cur_ch_mask = sdr->hw_ch_mask; + sdr->num_cur_ch = sdr->num_hw_ch; + dev_dbg(sdr->dev, "default fmt[%u]: mask %lu num %u\n", + i, sdr->cur_ch_mask, sdr->num_cur_ch); + return 0; + } + } + return -EINVAL; +} + +static int rcar_drif_enum_fmt_sdr_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index >= ARRAY_SIZE(formats)) + return -EINVAL; + + f->pixelformat = formats[f->index].pixelformat; + + return 0; +} + +static int rcar_drif_g_fmt_sdr_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rcar_drif_sdr *sdr = video_drvdata(file); + + f->fmt.sdr.pixelformat = sdr->fmt->pixelformat; + f->fmt.sdr.buffersize = sdr->fmt->buffersize; + + return 0; +} + +static int rcar_drif_s_fmt_sdr_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rcar_drif_sdr *sdr = video_drvdata(file); + struct vb2_queue *q = &sdr->vb_queue; + unsigned int i; + + if (vb2_is_busy(q)) + return -EBUSY; + + for (i = 0; i < ARRAY_SIZE(formats); i++) { + if (formats[i].pixelformat == f->fmt.sdr.pixelformat) + break; + } + + if (i == ARRAY_SIZE(formats)) + i = 0; /* Set the 1st format as default on no match */ + + sdr->fmt = &formats[i]; + f->fmt.sdr.pixelformat = sdr->fmt->pixelformat; + f->fmt.sdr.buffersize = formats[i].buffersize; + memset(f->fmt.sdr.reserved, 0, sizeof(f->fmt.sdr.reserved)); + + /* + * If a format demands one channel only out of two + * enabled channels, pick the 0th channel. + */ + if (formats[i].num_ch < sdr->num_hw_ch) { + sdr->cur_ch_mask = BIT(0); + sdr->num_cur_ch = formats[i].num_ch; + } else { + sdr->cur_ch_mask = sdr->hw_ch_mask; + sdr->num_cur_ch = sdr->num_hw_ch; + } + + rdrif_dbg(sdr, "cur: idx %u mask %lu num %u\n", + i, sdr->cur_ch_mask, sdr->num_cur_ch); + + return 0; +} + +static int rcar_drif_try_fmt_sdr_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(formats); i++) { + if (formats[i].pixelformat == f->fmt.sdr.pixelformat) { + f->fmt.sdr.buffersize = formats[i].buffersize; + return 0; + } + } + + f->fmt.sdr.pixelformat = formats[0].pixelformat; + f->fmt.sdr.buffersize = formats[0].buffersize; + memset(f->fmt.sdr.reserved, 0, sizeof(f->fmt.sdr.reserved)); + + return 0; +} + +/* Tuner subdev ioctls */ +static int rcar_drif_enum_freq_bands(struct file *file, void *priv, + struct v4l2_frequency_band *band) +{ + struct rcar_drif_sdr *sdr = video_drvdata(file); + + return v4l2_subdev_call(sdr->ep.subdev, tuner, enum_freq_bands, band); +} + +static int rcar_drif_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct rcar_drif_sdr *sdr = video_drvdata(file); + + return v4l2_subdev_call(sdr->ep.subdev, tuner, g_frequency, f); +} + +static int rcar_drif_s_frequency(struct file *file, void *priv, + const struct v4l2_frequency *f) +{ + struct rcar_drif_sdr *sdr = video_drvdata(file); + + return v4l2_subdev_call(sdr->ep.subdev, tuner, s_frequency, f); +} + +static int rcar_drif_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *vt) +{ + struct rcar_drif_sdr *sdr = video_drvdata(file); + + return v4l2_subdev_call(sdr->ep.subdev, tuner, g_tuner, vt); +} + +static int rcar_drif_s_tuner(struct file *file, void *priv, + const struct v4l2_tuner *vt) +{ + struct rcar_drif_sdr *sdr = video_drvdata(file); + + return v4l2_subdev_call(sdr->ep.subdev, tuner, s_tuner, vt); +} + +static const struct v4l2_ioctl_ops rcar_drif_ioctl_ops = { + .vidioc_querycap = rcar_drif_querycap, + + .vidioc_enum_fmt_sdr_cap = rcar_drif_enum_fmt_sdr_cap, + .vidioc_g_fmt_sdr_cap = rcar_drif_g_fmt_sdr_cap, + .vidioc_s_fmt_sdr_cap = rcar_drif_s_fmt_sdr_cap, + .vidioc_try_fmt_sdr_cap = rcar_drif_try_fmt_sdr_cap, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + + .vidioc_s_frequency = rcar_drif_s_frequency, + .vidioc_g_frequency = rcar_drif_g_frequency, + .vidioc_s_tuner = rcar_drif_s_tuner, + .vidioc_g_tuner = rcar_drif_g_tuner, + .vidioc_enum_freq_bands = rcar_drif_enum_freq_bands, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + .vidioc_log_status = v4l2_ctrl_log_status, +}; + +static const struct v4l2_file_operations rcar_drif_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .unlocked_ioctl = video_ioctl2, +}; + +static int rcar_drif_sdr_register(struct rcar_drif_sdr *sdr) +{ + int ret; + + /* Init video_device structure */ + sdr->vdev = video_device_alloc(); + if (!sdr->vdev) + return -ENOMEM; + + snprintf(sdr->vdev->name, sizeof(sdr->vdev->name), "R-Car DRIF"); + sdr->vdev->fops = &rcar_drif_fops; + sdr->vdev->ioctl_ops = &rcar_drif_ioctl_ops; + sdr->vdev->release = video_device_release; + sdr->vdev->lock = &sdr->v4l2_mutex; + sdr->vdev->queue = &sdr->vb_queue; + sdr->vdev->queue->lock = &sdr->vb_queue_mutex; + sdr->vdev->ctrl_handler = &sdr->ctrl_hdl; + sdr->vdev->v4l2_dev = &sdr->v4l2_dev; + sdr->vdev->device_caps = V4L2_CAP_SDR_CAPTURE | V4L2_CAP_TUNER | + V4L2_CAP_STREAMING | V4L2_CAP_READWRITE; + video_set_drvdata(sdr->vdev, sdr); + + /* Register V4L2 SDR device */ + ret = video_register_device(sdr->vdev, VFL_TYPE_SDR, -1); + if (ret) { + video_device_release(sdr->vdev); + sdr->vdev = NULL; + dev_err(sdr->dev, "failed video_register_device (%d)\n", ret); + } + + return ret; +} + +static void rcar_drif_sdr_unregister(struct rcar_drif_sdr *sdr) +{ + video_unregister_device(sdr->vdev); + sdr->vdev = NULL; +} + +/* Sub-device bound callback */ +static int rcar_drif_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct rcar_drif_sdr *sdr = + container_of(notifier, struct rcar_drif_sdr, notifier); + + if (sdr->ep.asd.match.fwnode.fwnode != + of_fwnode_handle(subdev->dev->of_node)) { + rdrif_err(sdr, "subdev %s cannot bind\n", subdev->name); + return -EINVAL; + } + + v4l2_set_subdev_hostdata(subdev, sdr); + sdr->ep.subdev = subdev; + rdrif_dbg(sdr, "bound asd %s\n", subdev->name); + + return 0; +} + +/* Sub-device unbind callback */ +static void rcar_drif_notify_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct rcar_drif_sdr *sdr = + container_of(notifier, struct rcar_drif_sdr, notifier); + + if (sdr->ep.subdev != subdev) { + rdrif_err(sdr, "subdev %s is not bound\n", subdev->name); + return; + } + + /* Free ctrl handler if initialized */ + v4l2_ctrl_handler_free(&sdr->ctrl_hdl); + sdr->v4l2_dev.ctrl_handler = NULL; + sdr->ep.subdev = NULL; + + rcar_drif_sdr_unregister(sdr); + rdrif_dbg(sdr, "unbind asd %s\n", subdev->name); +} + +/* Sub-device registered notification callback */ +static int rcar_drif_notify_complete(struct v4l2_async_notifier *notifier) +{ + struct rcar_drif_sdr *sdr = + container_of(notifier, struct rcar_drif_sdr, notifier); + int ret; + + /* + * The subdev tested at this point uses 4 controls. Using 10 as a worst + * case scenario hint. When less controls are needed there will be some + * unused memory and when more controls are needed the framework uses + * hash to manage controls within this number. + */ + ret = v4l2_ctrl_handler_init(&sdr->ctrl_hdl, 10); + if (ret) + return -ENOMEM; + + sdr->v4l2_dev.ctrl_handler = &sdr->ctrl_hdl; + ret = v4l2_device_register_subdev_nodes(&sdr->v4l2_dev); + if (ret) { + rdrif_err(sdr, "failed: register subdev nodes ret %d\n", ret); + goto error; + } + + ret = v4l2_ctrl_add_handler(&sdr->ctrl_hdl, + sdr->ep.subdev->ctrl_handler, NULL); + if (ret) { + rdrif_err(sdr, "failed: ctrl add hdlr ret %d\n", ret); + goto error; + } + + ret = rcar_drif_sdr_register(sdr); + if (ret) + goto error; + + return ret; + +error: + v4l2_ctrl_handler_free(&sdr->ctrl_hdl); + + return ret; +} + +/* Read endpoint properties */ +static void rcar_drif_get_ep_properties(struct rcar_drif_sdr *sdr, + struct fwnode_handle *fwnode) +{ + u32 val; + + /* Set the I2S defaults for SIRMDR1*/ + sdr->mdr1 = RCAR_DRIF_SIRMDR1_SYNCMD_LR | RCAR_DRIF_SIRMDR1_MSB_FIRST | + RCAR_DRIF_SIRMDR1_DTDL_1 | RCAR_DRIF_SIRMDR1_SYNCDL_0; + + /* Parse sync polarity from endpoint */ + if (!fwnode_property_read_u32(fwnode, "sync-active", &val)) + sdr->mdr1 |= val ? RCAR_DRIF_SIRMDR1_SYNCAC_POL_HIGH : + RCAR_DRIF_SIRMDR1_SYNCAC_POL_LOW; + else + sdr->mdr1 |= RCAR_DRIF_SIRMDR1_SYNCAC_POL_HIGH; /* default */ + + dev_dbg(sdr->dev, "mdr1 0x%08x\n", sdr->mdr1); +} + +/* Parse sub-devs (tuner) to find a matching device */ +static int rcar_drif_parse_subdevs(struct rcar_drif_sdr *sdr) +{ + struct v4l2_async_notifier *notifier = &sdr->notifier; + struct fwnode_handle *fwnode, *ep; + + notifier->subdevs = devm_kzalloc(sdr->dev, sizeof(*notifier->subdevs), + GFP_KERNEL); + if (!notifier->subdevs) + return -ENOMEM; + + ep = fwnode_graph_get_next_endpoint(of_fwnode_handle(sdr->dev->of_node), + NULL); + if (!ep) + return 0; + + notifier->subdevs[notifier->num_subdevs] = &sdr->ep.asd; + fwnode = fwnode_graph_get_remote_port_parent(ep); + if (!fwnode) { + dev_warn(sdr->dev, "bad remote port parent\n"); + fwnode_handle_put(ep); + return -EINVAL; + } + + sdr->ep.asd.match.fwnode.fwnode = fwnode; + sdr->ep.asd.match_type = V4L2_ASYNC_MATCH_FWNODE; + notifier->num_subdevs++; + + /* Get the endpoint properties */ + rcar_drif_get_ep_properties(sdr, ep); + + fwnode_handle_put(fwnode); + fwnode_handle_put(ep); + + return 0; +} + +/* Check if the given device is the primary bond */ +static bool rcar_drif_primary_bond(struct platform_device *pdev) +{ + return of_property_read_bool(pdev->dev.of_node, "renesas,primary-bond"); +} + +/* Check if both devices of the bond are enabled */ +static struct device_node *rcar_drif_bond_enabled(struct platform_device *p) +{ + struct device_node *np; + + np = of_parse_phandle(p->dev.of_node, "renesas,bonding", 0); + if (np && of_device_is_available(np)) + return np; + + return NULL; +} + +/* Check if the bonded device is probed */ +static int rcar_drif_bond_available(struct rcar_drif_sdr *sdr, + struct device_node *np) +{ + struct platform_device *pdev; + struct rcar_drif *ch; + int ret = 0; + + pdev = of_find_device_by_node(np); + if (!pdev) { + dev_err(sdr->dev, "failed to get bonded device from node\n"); + return -ENODEV; + } + + device_lock(&pdev->dev); + ch = platform_get_drvdata(pdev); + if (ch) { + /* Update sdr data in the bonded device */ + ch->sdr = sdr; + + /* Update sdr with bonded device data */ + sdr->ch[ch->num] = ch; + sdr->hw_ch_mask |= BIT(ch->num); + } else { + /* Defer */ + dev_info(sdr->dev, "defer probe\n"); + ret = -EPROBE_DEFER; + } + device_unlock(&pdev->dev); + + put_device(&pdev->dev); + + return ret; +} + +/* V4L2 SDR device probe */ +static int rcar_drif_sdr_probe(struct rcar_drif_sdr *sdr) +{ + int ret; + + /* Validate any supported format for enabled channels */ + ret = rcar_drif_set_default_format(sdr); + if (ret) { + dev_err(sdr->dev, "failed to set default format\n"); + return ret; + } + + /* Set defaults */ + sdr->hwbuf_size = RCAR_DRIF_DEFAULT_HWBUF_SIZE; + + mutex_init(&sdr->v4l2_mutex); + mutex_init(&sdr->vb_queue_mutex); + spin_lock_init(&sdr->queued_bufs_lock); + spin_lock_init(&sdr->dma_lock); + INIT_LIST_HEAD(&sdr->queued_bufs); + + /* Init videobuf2 queue structure */ + sdr->vb_queue.type = V4L2_BUF_TYPE_SDR_CAPTURE; + sdr->vb_queue.io_modes = VB2_READ | VB2_MMAP | VB2_DMABUF; + sdr->vb_queue.drv_priv = sdr; + sdr->vb_queue.buf_struct_size = sizeof(struct rcar_drif_frame_buf); + sdr->vb_queue.ops = &rcar_drif_vb2_ops; + sdr->vb_queue.mem_ops = &vb2_vmalloc_memops; + sdr->vb_queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + + /* Init videobuf2 queue */ + ret = vb2_queue_init(&sdr->vb_queue); + if (ret) { + dev_err(sdr->dev, "failed: vb2_queue_init ret %d\n", ret); + return ret; + } + + /* Register the v4l2_device */ + ret = v4l2_device_register(sdr->dev, &sdr->v4l2_dev); + if (ret) { + dev_err(sdr->dev, "failed: v4l2_device_register ret %d\n", ret); + return ret; + } + + /* + * Parse subdevs after v4l2_device_register because if the subdev + * is already probed, bound and complete will be called immediately + */ + ret = rcar_drif_parse_subdevs(sdr); + if (ret) + goto error; + + sdr->notifier.bound = rcar_drif_notify_bound; + sdr->notifier.unbind = rcar_drif_notify_unbind; + sdr->notifier.complete = rcar_drif_notify_complete; + + /* Register notifier */ + ret = v4l2_async_notifier_register(&sdr->v4l2_dev, &sdr->notifier); + if (ret < 0) { + dev_err(sdr->dev, "failed: notifier register ret %d\n", ret); + goto error; + } + + return ret; + +error: + v4l2_device_unregister(&sdr->v4l2_dev); + + return ret; +} + +/* V4L2 SDR device remove */ +static void rcar_drif_sdr_remove(struct rcar_drif_sdr *sdr) +{ + v4l2_async_notifier_unregister(&sdr->notifier); + v4l2_device_unregister(&sdr->v4l2_dev); +} + +/* DRIF channel probe */ +static int rcar_drif_probe(struct platform_device *pdev) +{ + struct rcar_drif_sdr *sdr; + struct device_node *np; + struct rcar_drif *ch; + struct resource *res; + int ret; + + /* Reserve memory for enabled channel */ + ch = devm_kzalloc(&pdev->dev, sizeof(*ch), GFP_KERNEL); + if (!ch) + return -ENOMEM; + + ch->pdev = pdev; + + /* Module clock */ + ch->clk = devm_clk_get(&pdev->dev, "fck"); + if (IS_ERR(ch->clk)) { + ret = PTR_ERR(ch->clk); + dev_err(&pdev->dev, "clk get failed (%d)\n", ret); + return ret; + } + + /* Register map */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ch->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(ch->base)) { + ret = PTR_ERR(ch->base); + dev_err(&pdev->dev, "ioremap failed (%d)\n", ret); + return ret; + } + ch->start = res->start; + platform_set_drvdata(pdev, ch); + + /* Check if both channels of the bond are enabled */ + np = rcar_drif_bond_enabled(pdev); + if (np) { + /* Check if current channel acting as primary-bond */ + if (!rcar_drif_primary_bond(pdev)) { + ch->num = 1; /* Primary bond is channel 0 always */ + of_node_put(np); + return 0; + } + } + + /* Reserve memory for SDR structure */ + sdr = devm_kzalloc(&pdev->dev, sizeof(*sdr), GFP_KERNEL); + if (!sdr) { + of_node_put(np); + return -ENOMEM; + } + ch->sdr = sdr; + sdr->dev = &pdev->dev; + + /* Establish links between SDR and channel(s) */ + sdr->ch[ch->num] = ch; + sdr->hw_ch_mask = BIT(ch->num); + if (np) { + /* Check if bonded device is ready */ + ret = rcar_drif_bond_available(sdr, np); + of_node_put(np); + if (ret) + return ret; + } + sdr->num_hw_ch = hweight_long(sdr->hw_ch_mask); + + return rcar_drif_sdr_probe(sdr); +} + +/* DRIF channel remove */ +static int rcar_drif_remove(struct platform_device *pdev) +{ + struct rcar_drif *ch = platform_get_drvdata(pdev); + struct rcar_drif_sdr *sdr = ch->sdr; + + /* Channel 0 will be the SDR instance */ + if (ch->num) + return 0; + + /* SDR instance */ + rcar_drif_sdr_remove(sdr); + + return 0; +} + +/* FIXME: Implement suspend/resume support */ +static int __maybe_unused rcar_drif_suspend(struct device *dev) +{ + return 0; +} + +static int __maybe_unused rcar_drif_resume(struct device *dev) +{ + return 0; +} + +static SIMPLE_DEV_PM_OPS(rcar_drif_pm_ops, rcar_drif_suspend, + rcar_drif_resume); + +static const struct of_device_id rcar_drif_of_table[] = { + { .compatible = "renesas,rcar-gen3-drif" }, + { } +}; +MODULE_DEVICE_TABLE(of, rcar_drif_of_table); + +#define RCAR_DRIF_DRV_NAME "rcar_drif" +static struct platform_driver rcar_drif_driver = { + .driver = { + .name = RCAR_DRIF_DRV_NAME, + .of_match_table = of_match_ptr(rcar_drif_of_table), + .pm = &rcar_drif_pm_ops, + }, + .probe = rcar_drif_probe, + .remove = rcar_drif_remove, +}; + +module_platform_driver(rcar_drif_driver); + +MODULE_DESCRIPTION("Renesas R-Car Gen3 DRIF driver"); +MODULE_ALIAS("platform:" RCAR_DRIF_DRV_NAME); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Ramesh Shanmugasundaram <ramesh.shanmugasundaram@bp.renesas.com>"); diff --git a/drivers/media/platform/rcar_fdp1.c b/drivers/media/platform/rcar_fdp1.c index 42f25d241edd..0da0eba9202c 100644 --- a/drivers/media/platform/rcar_fdp1.c +++ b/drivers/media/platform/rcar_fdp1.c @@ -1,5 +1,5 @@ /* - * Renesas RCar Fine Display Processor + * Renesas R-Car Fine Display Processor * * Video format converter and frame deinterlacer device. * diff --git a/drivers/media/platform/s3c-camif/camif-capture.c b/drivers/media/platform/s3c-camif/camif-capture.c index 1b30be72f4f9..25c7a7d42292 100644 --- a/drivers/media/platform/s3c-camif/camif-capture.c +++ b/drivers/media/platform/s3c-camif/camif-capture.c @@ -80,7 +80,7 @@ static int s3c_camif_hw_init(struct camif_dev *camif, struct camif_vp *vp) camif_hw_set_test_pattern(camif, camif->test_pattern); if (variant->has_img_effect) camif_hw_set_effect(camif, camif->colorfx, - camif->colorfx_cb, camif->colorfx_cr); + camif->colorfx_cr, camif->colorfx_cb); if (variant->ip_revision == S3C6410_CAMIF_IP_REV) camif_hw_set_input_path(vp); camif_cfg_video_path(vp); @@ -364,7 +364,7 @@ irqreturn_t s3c_camif_irq_handler(int irq, void *priv) camif_hw_set_test_pattern(camif, camif->test_pattern); if (camif->variant->has_img_effect) camif_hw_set_effect(camif, camif->colorfx, - camif->colorfx_cb, camif->colorfx_cr); + camif->colorfx_cr, camif->colorfx_cb); vp->state &= ~ST_VP_CONFIG; } unlock: diff --git a/drivers/media/platform/s5p-cec/s5p_cec.c b/drivers/media/platform/s5p-cec/s5p_cec.c index 664937b61fa4..8e06071a7977 100644 --- a/drivers/media/platform/s5p-cec/s5p_cec.c +++ b/drivers/media/platform/s5p-cec/s5p_cec.c @@ -173,6 +173,7 @@ static int s5p_cec_probe(struct platform_device *pdev) struct platform_device *hdmi_dev; struct resource *res; struct s5p_cec_dev *cec; + bool needs_hpd = of_property_read_bool(pdev->dev.of_node, "needs-hpd"); int ret; np = of_parse_phandle(pdev->dev.of_node, "hdmi-phandle", 0); @@ -221,7 +222,8 @@ static int s5p_cec_probe(struct platform_device *pdev) cec->adap = cec_allocate_adapter(&s5p_cec_adap_ops, cec, CEC_NAME, CEC_CAP_LOG_ADDRS | CEC_CAP_TRANSMIT | - CEC_CAP_PASSTHROUGH | CEC_CAP_RC, 1); + CEC_CAP_PASSTHROUGH | CEC_CAP_RC | + (needs_hpd ? CEC_CAP_NEEDS_HPD : 0), 1); ret = PTR_ERR_OR_ZERO(cec->adap); if (ret) return ret; @@ -235,7 +237,7 @@ static int s5p_cec_probe(struct platform_device *pdev) platform_set_drvdata(pdev, cec); pm_runtime_enable(dev); - dev_dbg(dev, "successfuly probed\n"); + dev_dbg(dev, "successfully probed\n"); return 0; err_delete_adapter: diff --git a/drivers/media/platform/s5p-cec/s5p_cec.h b/drivers/media/platform/s5p-cec/s5p_cec.h index 7015845c1caa..8bcd8dc1aeb9 100644 --- a/drivers/media/platform/s5p-cec/s5p_cec.h +++ b/drivers/media/platform/s5p-cec/s5p_cec.h @@ -22,7 +22,6 @@ #include <linux/platform_device.h> #include <linux/pm_runtime.h> #include <linux/timer.h> -#include <linux/version.h> #include <linux/workqueue.h> #include <media/cec.h> diff --git a/drivers/media/platform/s5p-jpeg/jpeg-core.c b/drivers/media/platform/s5p-jpeg/jpeg-core.c index 52dc7941db65..d1e3ebb22577 100644 --- a/drivers/media/platform/s5p-jpeg/jpeg-core.c +++ b/drivers/media/platform/s5p-jpeg/jpeg-core.c @@ -1099,10 +1099,10 @@ static bool s5p_jpeg_parse_hdr(struct s5p_jpeg_q_data *result, struct s5p_jpeg_ctx *ctx) { int c, components = 0, notfound, n_dht = 0, n_dqt = 0; - unsigned int height, width, word, subsampling = 0, sos = 0, sof = 0, - sof_len = 0; - unsigned int dht[S5P_JPEG_MAX_MARKER], dht_len[S5P_JPEG_MAX_MARKER], - dqt[S5P_JPEG_MAX_MARKER], dqt_len[S5P_JPEG_MAX_MARKER]; + unsigned int height = 0, width = 0, word, subsampling = 0; + unsigned int sos = 0, sof = 0, sof_len = 0; + unsigned int dht[S5P_JPEG_MAX_MARKER], dht_len[S5P_JPEG_MAX_MARKER]; + unsigned int dqt[S5P_JPEG_MAX_MARKER], dqt_len[S5P_JPEG_MAX_MARKER]; long length; struct s5p_jpeg_buffer jpeg_buffer; @@ -2642,13 +2642,13 @@ static irqreturn_t s5p_jpeg_irq(int irq, void *dev_id) if (curr_ctx->mode == S5P_JPEG_ENCODE) vb2_set_plane_payload(&dst_buf->vb2_buf, 0, payload_size); v4l2_m2m_buf_done(dst_buf, state); - v4l2_m2m_job_finish(jpeg->m2m_dev, curr_ctx->fh.m2m_ctx); curr_ctx->subsampling = s5p_jpeg_get_subsampling_mode(jpeg->regs); spin_unlock(&jpeg->slock); s5p_jpeg_clear_int(jpeg->regs); + v4l2_m2m_job_finish(jpeg->m2m_dev, curr_ctx->fh.m2m_ctx); return IRQ_HANDLED; } @@ -2707,11 +2707,12 @@ static irqreturn_t exynos4_jpeg_irq(int irq, void *priv) v4l2_m2m_buf_done(dst_vb, VB2_BUF_STATE_ERROR); } - v4l2_m2m_job_finish(jpeg->m2m_dev, curr_ctx->fh.m2m_ctx); if (jpeg->variant->version == SJPEG_EXYNOS4) curr_ctx->subsampling = exynos4_jpeg_get_frame_fmt(jpeg->regs); spin_unlock(&jpeg->slock); + + v4l2_m2m_job_finish(jpeg->m2m_dev, curr_ctx->fh.m2m_ctx); return IRQ_HANDLED; } @@ -2770,10 +2771,15 @@ static irqreturn_t exynos3250_jpeg_irq(int irq, void *dev_id) if (curr_ctx->mode == S5P_JPEG_ENCODE) vb2_set_plane_payload(&dst_buf->vb2_buf, 0, payload_size); v4l2_m2m_buf_done(dst_buf, state); - v4l2_m2m_job_finish(jpeg->m2m_dev, curr_ctx->fh.m2m_ctx); curr_ctx->subsampling = exynos3250_jpeg_get_subsampling_mode(jpeg->regs); + + spin_unlock(&jpeg->slock); + + v4l2_m2m_job_finish(jpeg->m2m_dev, curr_ctx->fh.m2m_ctx); + return IRQ_HANDLED; + exit_unlock: spin_unlock(&jpeg->slock); return IRQ_HANDLED; diff --git a/drivers/media/platform/s5p-mfc/s5p_mfc_opr_v5.c b/drivers/media/platform/s5p-mfc/s5p_mfc_opr_v5.c index b41ee608c171..0913881219ff 100644 --- a/drivers/media/platform/s5p-mfc/s5p_mfc_opr_v5.c +++ b/drivers/media/platform/s5p-mfc/s5p_mfc_opr_v5.c @@ -1293,7 +1293,7 @@ static int s5p_mfc_run_init_dec_buffers(struct s5p_mfc_ctx *ctx) * First set the output frame buffers */ if (ctx->capture_state != QUEUE_BUFS_MMAPED) { - mfc_err("It seems that not all destionation buffers were mmaped\nMFC requires that all destination are mmaped before starting processing\n"); + mfc_err("It seems that not all destination buffers were mmaped\nMFC requires that all destination are mmaped before starting processing\n"); return -EAGAIN; } if (list_empty(&ctx->src_queue)) { diff --git a/drivers/media/platform/s5p-mfc/s5p_mfc_opr_v6.c b/drivers/media/platform/s5p-mfc/s5p_mfc_opr_v6.c index 85880e9106be..88dbb9c341ec 100644 --- a/drivers/media/platform/s5p-mfc/s5p_mfc_opr_v6.c +++ b/drivers/media/platform/s5p-mfc/s5p_mfc_opr_v6.c @@ -1650,7 +1650,7 @@ static inline int s5p_mfc_run_init_dec_buffers(struct s5p_mfc_ctx *ctx) * s5p_mfc_alloc_dec_buffers(ctx); */ if (ctx->capture_state != QUEUE_BUFS_MMAPED) { - mfc_err("It seems that not all destionation buffers were\n" + mfc_err("It seems that not all destination buffers were\n" "mmaped.MFC requires that all destination are mmaped\n" "before starting processing.\n"); return -EAGAIN; diff --git a/drivers/media/platform/sh_vou.c b/drivers/media/platform/sh_vou.c index 992d61a8b961..871da2a2a91c 100644 --- a/drivers/media/platform/sh_vou.c +++ b/drivers/media/platform/sh_vou.c @@ -229,6 +229,7 @@ static void sh_vou_stream_config(struct sh_vou_device *vou_dev) break; case V4L2_PIX_FMT_RGB565: dataswap ^= 1; + /* fall through */ case V4L2_PIX_FMT_RGB565X: row_coeff = 2; break; @@ -815,6 +816,7 @@ static u32 sh_vou_ntsc_mode(enum sh_vou_bus_fmt bus_fmt) default: pr_warn("%s(): Invalid bus-format code %d, using default 8-bit\n", __func__, bus_fmt); + /* fall through */ case SH_VOU_BUS_8BIT: return 1; case SH_VOU_BUS_16BIT: diff --git a/drivers/media/platform/soc_camera/soc_camera.c b/drivers/media/platform/soc_camera/soc_camera.c index 3c9421f4d8e3..45a0429d75bb 100644 --- a/drivers/media/platform/soc_camera/soc_camera.c +++ b/drivers/media/platform/soc_camera/soc_camera.c @@ -23,6 +23,7 @@ #include <linux/list.h> #include <linux/module.h> #include <linux/mutex.h> +#include <linux/of_graph.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> #include <linux/regulator/consumer.h> @@ -36,7 +37,7 @@ #include <media/v4l2-common.h> #include <media/v4l2-ioctl.h> #include <media/v4l2-dev.h> -#include <media/v4l2-of.h> +#include <media/v4l2-fwnode.h> #include <media/videobuf2-v4l2.h> /* Default to VGA resolution */ @@ -1512,8 +1513,8 @@ static int soc_of_bind(struct soc_camera_host *ici, if (!info) return -ENOMEM; - info->sasd.asd.match.of.node = remote; - info->sasd.asd.match_type = V4L2_ASYNC_MATCH_OF; + info->sasd.asd.match.fwnode.fwnode = of_fwnode_handle(remote); + info->sasd.asd.match_type = V4L2_ASYNC_MATCH_FWNODE; info->subdev = &info->sasd.asd; /* Or shall this be managed by the soc-camera device? */ diff --git a/drivers/media/platform/soc_camera/soc_mediabus.c b/drivers/media/platform/soc_camera/soc_mediabus.c index e3e665e1c503..57581f626f4c 100644 --- a/drivers/media/platform/soc_camera/soc_mediabus.c +++ b/drivers/media/platform/soc_camera/soc_mediabus.c @@ -494,6 +494,7 @@ unsigned int soc_mbus_config_compatible(const struct v4l2_mbus_config *cfg, V4L2_MBUS_HSYNC_ACTIVE_LOW); vsync = common_flags & (V4L2_MBUS_VSYNC_ACTIVE_HIGH | V4L2_MBUS_VSYNC_ACTIVE_LOW); + /* fall through */ case V4L2_MBUS_BT656: pclk = common_flags & (V4L2_MBUS_PCLK_SAMPLE_RISING | V4L2_MBUS_PCLK_SAMPLE_FALLING); diff --git a/drivers/media/platform/sti/cec/stih-cec.c b/drivers/media/platform/sti/cec/stih-cec.c index 39ff55145a82..dccbdaebb7a8 100644 --- a/drivers/media/platform/sti/cec/stih-cec.c +++ b/drivers/media/platform/sti/cec/stih-cec.c @@ -1,6 +1,6 @@ /* * STIH4xx CEC driver - * Copyright (C) STMicroelectronic SA 2016 + * Copyright (C) STMicroelectronics SA 2016 * * 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 @@ -213,7 +213,8 @@ static int stih_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, for (i = 0; i < msg->len; i++) writeb(msg->msg[i], cec->regs + CEC_TX_DATA_BASE + i); - /* Start transmission, configure hardware to add start and stop bits + /* + * Start transmission, configure hardware to add start and stop bits * Signal free time is handled by the hardware */ writel(CEC_TX_AUTO_SOM_EN | CEC_TX_AUTO_EOM_EN | CEC_TX_START | @@ -225,22 +226,21 @@ static int stih_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, static void stih_tx_done(struct stih_cec *cec, u32 status) { if (status & CEC_TX_ERROR) { - cec_transmit_done(cec->adap, CEC_TX_STATUS_ERROR, 0, 0, 0, 1); + cec_transmit_attempt_done(cec->adap, CEC_TX_STATUS_ERROR); return; } if (status & CEC_TX_ARB_ERROR) { - cec_transmit_done(cec->adap, - CEC_TX_STATUS_ARB_LOST, 1, 0, 0, 0); + cec_transmit_attempt_done(cec->adap, CEC_TX_STATUS_ARB_LOST); return; } if (!(status & CEC_TX_ACK_GET_STS)) { - cec_transmit_done(cec->adap, CEC_TX_STATUS_NACK, 0, 1, 0, 0); + cec_transmit_attempt_done(cec->adap, CEC_TX_STATUS_NACK); return; } - cec_transmit_done(cec->adap, CEC_TX_STATUS_OK, 0, 0, 0, 0); + cec_transmit_attempt_done(cec->adap, CEC_TX_STATUS_OK); } static void stih_rx_done(struct stih_cec *cec, u32 status) @@ -353,7 +353,7 @@ static int stih_cec_probe(struct platform_device *pdev) cec->adap = cec_allocate_adapter(&sti_cec_adap_ops, cec, CEC_NAME, CEC_CAP_LOG_ADDRS | CEC_CAP_PASSTHROUGH | - CEC_CAP_TRANSMIT, 1); + CEC_CAP_TRANSMIT, CEC_MAX_LOG_ADDRS); ret = PTR_ERR_OR_ZERO(cec->adap); if (ret) return ret; diff --git a/drivers/media/platform/stm32/Makefile b/drivers/media/platform/stm32/Makefile new file mode 100644 index 000000000000..07355091376b --- /dev/null +++ b/drivers/media/platform/stm32/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_VIDEO_STM32_DCMI) += stm32-dcmi.o +obj-$(CONFIG_VIDEO_STM32_HDMI_CEC) += stm32-cec.o diff --git a/drivers/media/platform/stm32/stm32-cec.c b/drivers/media/platform/stm32/stm32-cec.c new file mode 100644 index 000000000000..9ab896b01ee8 --- /dev/null +++ b/drivers/media/platform/stm32/stm32-cec.c @@ -0,0 +1,362 @@ +/* + * STM32 CEC driver + * Copyright (C) STMicroelectronics SA 2017 + * + * 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; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#include <media/cec.h> + +#define CEC_NAME "stm32-cec" + +/* CEC registers */ +#define CEC_CR 0x0000 /* Control Register */ +#define CEC_CFGR 0x0004 /* ConFiGuration Register */ +#define CEC_TXDR 0x0008 /* Rx data Register */ +#define CEC_RXDR 0x000C /* Rx data Register */ +#define CEC_ISR 0x0010 /* Interrupt and status Register */ +#define CEC_IER 0x0014 /* Interrupt enable Register */ + +#define TXEOM BIT(2) +#define TXSOM BIT(1) +#define CECEN BIT(0) + +#define LSTN BIT(31) +#define OAR GENMASK(30, 16) +#define SFTOP BIT(8) +#define BRDNOGEN BIT(7) +#define LBPEGEN BIT(6) +#define BREGEN BIT(5) +#define BRESTP BIT(4) +#define RXTOL BIT(3) +#define SFT GENMASK(2, 0) +#define FULL_CFG (LSTN | SFTOP | BRDNOGEN | LBPEGEN | BREGEN | BRESTP \ + | RXTOL) + +#define TXACKE BIT(12) +#define TXERR BIT(11) +#define TXUDR BIT(10) +#define TXEND BIT(9) +#define TXBR BIT(8) +#define ARBLST BIT(7) +#define RXACKE BIT(6) +#define RXOVR BIT(2) +#define RXEND BIT(1) +#define RXBR BIT(0) + +#define ALL_TX_IT (TXEND | TXBR | TXACKE | TXERR | TXUDR | ARBLST) +#define ALL_RX_IT (RXEND | RXBR | RXACKE | RXOVR) + +struct stm32_cec { + struct cec_adapter *adap; + struct device *dev; + struct clk *clk_cec; + struct clk *clk_hdmi_cec; + struct reset_control *rstc; + struct regmap *regmap; + int irq; + u32 irq_status; + struct cec_msg rx_msg; + struct cec_msg tx_msg; + int tx_cnt; +}; + +static void cec_hw_init(struct stm32_cec *cec) +{ + regmap_update_bits(cec->regmap, CEC_CR, TXEOM | TXSOM | CECEN, 0); + + regmap_update_bits(cec->regmap, CEC_IER, ALL_TX_IT | ALL_RX_IT, + ALL_TX_IT | ALL_RX_IT); + + regmap_update_bits(cec->regmap, CEC_CFGR, FULL_CFG, FULL_CFG); +} + +static void stm32_tx_done(struct stm32_cec *cec, u32 status) +{ + if (status & (TXERR | TXUDR)) { + cec_transmit_done(cec->adap, CEC_TX_STATUS_ERROR, + 0, 0, 0, 1); + return; + } + + if (status & ARBLST) { + cec_transmit_done(cec->adap, CEC_TX_STATUS_ARB_LOST, + 1, 0, 0, 0); + return; + } + + if (status & TXACKE) { + cec_transmit_done(cec->adap, CEC_TX_STATUS_NACK, + 0, 1, 0, 0); + return; + } + + if (cec->irq_status & TXBR) { + /* send next byte */ + if (cec->tx_cnt < cec->tx_msg.len) + regmap_write(cec->regmap, CEC_TXDR, + cec->tx_msg.msg[cec->tx_cnt++]); + + /* TXEOM is set to command transmission of the last byte */ + if (cec->tx_cnt == cec->tx_msg.len) + regmap_update_bits(cec->regmap, CEC_CR, TXEOM, TXEOM); + } + + if (cec->irq_status & TXEND) + cec_transmit_done(cec->adap, CEC_TX_STATUS_OK, 0, 0, 0, 0); +} + +static void stm32_rx_done(struct stm32_cec *cec, u32 status) +{ + if (cec->irq_status & (RXACKE | RXOVR)) { + cec->rx_msg.len = 0; + return; + } + + if (cec->irq_status & RXBR) { + u32 val; + + regmap_read(cec->regmap, CEC_RXDR, &val); + cec->rx_msg.msg[cec->rx_msg.len++] = val & 0xFF; + } + + if (cec->irq_status & RXEND) { + cec_received_msg(cec->adap, &cec->rx_msg); + cec->rx_msg.len = 0; + } +} + +static irqreturn_t stm32_cec_irq_thread(int irq, void *arg) +{ + struct stm32_cec *cec = arg; + + if (cec->irq_status & ALL_TX_IT) + stm32_tx_done(cec, cec->irq_status); + + if (cec->irq_status & ALL_RX_IT) + stm32_rx_done(cec, cec->irq_status); + + cec->irq_status = 0; + + return IRQ_HANDLED; +} + +static irqreturn_t stm32_cec_irq_handler(int irq, void *arg) +{ + struct stm32_cec *cec = arg; + + regmap_read(cec->regmap, CEC_ISR, &cec->irq_status); + + regmap_update_bits(cec->regmap, CEC_ISR, + ALL_TX_IT | ALL_RX_IT, + ALL_TX_IT | ALL_RX_IT); + + return IRQ_WAKE_THREAD; +} + +static int stm32_cec_adap_enable(struct cec_adapter *adap, bool enable) +{ + struct stm32_cec *cec = adap->priv; + int ret = 0; + + if (enable) { + ret = clk_enable(cec->clk_cec); + if (ret) + dev_err(cec->dev, "fail to enable cec clock\n"); + + clk_enable(cec->clk_hdmi_cec); + regmap_update_bits(cec->regmap, CEC_CR, CECEN, CECEN); + } else { + clk_disable(cec->clk_cec); + clk_disable(cec->clk_hdmi_cec); + regmap_update_bits(cec->regmap, CEC_CR, CECEN, 0); + } + + return ret; +} + +static int stm32_cec_adap_log_addr(struct cec_adapter *adap, u8 logical_addr) +{ + struct stm32_cec *cec = adap->priv; + u32 oar = (1 << logical_addr) << 16; + + regmap_update_bits(cec->regmap, CEC_CR, CECEN, 0); + + if (logical_addr == CEC_LOG_ADDR_INVALID) + regmap_update_bits(cec->regmap, CEC_CFGR, OAR, 0); + else + regmap_update_bits(cec->regmap, CEC_CFGR, oar, oar); + + regmap_update_bits(cec->regmap, CEC_CR, CECEN, CECEN); + + return 0; +} + +static int stm32_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, + u32 signal_free_time, struct cec_msg *msg) +{ + struct stm32_cec *cec = adap->priv; + + /* Copy message */ + cec->tx_msg = *msg; + cec->tx_cnt = 0; + + /* + * If the CEC message consists of only one byte, + * TXEOM must be set before of TXSOM. + */ + if (cec->tx_msg.len == 1) + regmap_update_bits(cec->regmap, CEC_CR, TXEOM, TXEOM); + + /* TXSOM is set to command transmission of the first byte */ + regmap_update_bits(cec->regmap, CEC_CR, TXSOM, TXSOM); + + /* Write the header (first byte of message) */ + regmap_write(cec->regmap, CEC_TXDR, cec->tx_msg.msg[0]); + cec->tx_cnt++; + + return 0; +} + +static const struct cec_adap_ops stm32_cec_adap_ops = { + .adap_enable = stm32_cec_adap_enable, + .adap_log_addr = stm32_cec_adap_log_addr, + .adap_transmit = stm32_cec_adap_transmit, +}; + +static const struct regmap_config stm32_cec_regmap_cfg = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = sizeof(u32), + .max_register = 0x14, + .fast_io = true, +}; + +static int stm32_cec_probe(struct platform_device *pdev) +{ + u32 caps = CEC_CAP_LOG_ADDRS | CEC_CAP_PASSTHROUGH | + CEC_CAP_TRANSMIT | CEC_CAP_RC | CEC_CAP_PHYS_ADDR | + CEC_MODE_MONITOR_ALL; + struct resource *res; + struct stm32_cec *cec; + void __iomem *mmio; + int ret; + + cec = devm_kzalloc(&pdev->dev, sizeof(*cec), GFP_KERNEL); + if (!cec) + return -ENOMEM; + + cec->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + mmio = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(mmio)) + return PTR_ERR(mmio); + + cec->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "cec", mmio, + &stm32_cec_regmap_cfg); + + if (IS_ERR(cec->regmap)) + return PTR_ERR(cec->regmap); + + cec->irq = platform_get_irq(pdev, 0); + if (cec->irq < 0) + return cec->irq; + + ret = devm_request_threaded_irq(&pdev->dev, cec->irq, + stm32_cec_irq_handler, + stm32_cec_irq_thread, + 0, + pdev->name, cec); + if (ret) + return ret; + + cec->clk_cec = devm_clk_get(&pdev->dev, "cec"); + if (IS_ERR(cec->clk_cec)) { + dev_err(&pdev->dev, "Cannot get cec clock\n"); + return PTR_ERR(cec->clk_cec); + } + + ret = clk_prepare(cec->clk_cec); + if (ret) { + dev_err(&pdev->dev, "Unable to prepare cec clock\n"); + return ret; + } + + cec->clk_hdmi_cec = devm_clk_get(&pdev->dev, "hdmi-cec"); + if (!IS_ERR(cec->clk_hdmi_cec)) { + ret = clk_prepare(cec->clk_hdmi_cec); + if (ret) { + dev_err(&pdev->dev, "Unable to prepare hdmi-cec clock\n"); + return ret; + } + } + + /* + * CEC_CAP_PHYS_ADDR caps should be removed when a cec notifier is + * available for example when a drm driver can provide edid + */ + cec->adap = cec_allocate_adapter(&stm32_cec_adap_ops, cec, + CEC_NAME, caps, CEC_MAX_LOG_ADDRS); + ret = PTR_ERR_OR_ZERO(cec->adap); + if (ret) + return ret; + + ret = cec_register_adapter(cec->adap, &pdev->dev); + if (ret) { + cec_delete_adapter(cec->adap); + return ret; + } + + cec_hw_init(cec); + + platform_set_drvdata(pdev, cec); + + return 0; +} + +static int stm32_cec_remove(struct platform_device *pdev) +{ + struct stm32_cec *cec = platform_get_drvdata(pdev); + + clk_unprepare(cec->clk_cec); + clk_unprepare(cec->clk_hdmi_cec); + + cec_unregister_adapter(cec->adap); + + return 0; +} + +static const struct of_device_id stm32_cec_of_match[] = { + { .compatible = "st,stm32-cec" }, + { /* end node */ } +}; +MODULE_DEVICE_TABLE(of, stm32_cec_of_match); + +static struct platform_driver stm32_cec_driver = { + .probe = stm32_cec_probe, + .remove = stm32_cec_remove, + .driver = { + .name = CEC_NAME, + .of_match_table = stm32_cec_of_match, + }, +}; + +module_platform_driver(stm32_cec_driver); + +MODULE_AUTHOR("Benjamin Gaignard <benjamin.gaignard@st.com>"); +MODULE_AUTHOR("Yannick Fertre <yannick.fertre@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics STM32 Consumer Electronics Control"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/stm32/stm32-dcmi.c b/drivers/media/platform/stm32/stm32-dcmi.c new file mode 100644 index 000000000000..83d32a5d0f40 --- /dev/null +++ b/drivers/media/platform/stm32/stm32-dcmi.c @@ -0,0 +1,1404 @@ +/* + * Driver for STM32 Digital Camera Memory Interface + * + * Copyright (C) STMicroelectronics SA 2017 + * Authors: Yannick Fertre <yannick.fertre@st.com> + * Hugues Fruchet <hugues.fruchet@st.com> + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + * + * This driver is based on atmel_isi.c + * + */ + +#include <linux/clk.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/dmaengine.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> +#include <linux/reset.h> +#include <linux/videodev2.h> + +#include <media/v4l2-ctrls.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-image-sizes.h> +#include <media/v4l2-ioctl.h> +#include <media/videobuf2-dma-contig.h> + +#define DRV_NAME "stm32-dcmi" + +/* Registers offset for DCMI */ +#define DCMI_CR 0x00 /* Control Register */ +#define DCMI_SR 0x04 /* Status Register */ +#define DCMI_RIS 0x08 /* Raw Interrupt Status register */ +#define DCMI_IER 0x0C /* Interrupt Enable Register */ +#define DCMI_MIS 0x10 /* Masked Interrupt Status register */ +#define DCMI_ICR 0x14 /* Interrupt Clear Register */ +#define DCMI_ESCR 0x18 /* Embedded Synchronization Code Register */ +#define DCMI_ESUR 0x1C /* Embedded Synchronization Unmask Register */ +#define DCMI_CWSTRT 0x20 /* Crop Window STaRT */ +#define DCMI_CWSIZE 0x24 /* Crop Window SIZE */ +#define DCMI_DR 0x28 /* Data Register */ +#define DCMI_IDR 0x2C /* IDentifier Register */ + +/* Bits definition for control register (DCMI_CR) */ +#define CR_CAPTURE BIT(0) +#define CR_CM BIT(1) +#define CR_CROP BIT(2) +#define CR_JPEG BIT(3) +#define CR_ESS BIT(4) +#define CR_PCKPOL BIT(5) +#define CR_HSPOL BIT(6) +#define CR_VSPOL BIT(7) +#define CR_FCRC_0 BIT(8) +#define CR_FCRC_1 BIT(9) +#define CR_EDM_0 BIT(10) +#define CR_EDM_1 BIT(11) +#define CR_ENABLE BIT(14) + +/* Bits definition for status register (DCMI_SR) */ +#define SR_HSYNC BIT(0) +#define SR_VSYNC BIT(1) +#define SR_FNE BIT(2) + +/* + * Bits definition for interrupt registers + * (DCMI_RIS, DCMI_IER, DCMI_MIS, DCMI_ICR) + */ +#define IT_FRAME BIT(0) +#define IT_OVR BIT(1) +#define IT_ERR BIT(2) +#define IT_VSYNC BIT(3) +#define IT_LINE BIT(4) + +enum state { + STOPPED = 0, + RUNNING, + STOPPING, +}; + +#define MIN_WIDTH 16U +#define MAX_WIDTH 2048U +#define MIN_HEIGHT 16U +#define MAX_HEIGHT 2048U + +#define TIMEOUT_MS 1000 + +struct dcmi_graph_entity { + struct device_node *node; + + struct v4l2_async_subdev asd; + struct v4l2_subdev *subdev; +}; + +struct dcmi_format { + u32 fourcc; + u32 mbus_code; + u8 bpp; +}; + +struct dcmi_buf { + struct vb2_v4l2_buffer vb; + bool prepared; + dma_addr_t paddr; + size_t size; + struct list_head list; +}; + +struct stm32_dcmi { + /* Protects the access of variables shared within the interrupt */ + spinlock_t irqlock; + struct device *dev; + void __iomem *regs; + struct resource *res; + struct reset_control *rstc; + int sequence; + struct list_head buffers; + struct dcmi_buf *active; + + struct v4l2_device v4l2_dev; + struct video_device *vdev; + struct v4l2_async_notifier notifier; + struct dcmi_graph_entity entity; + struct v4l2_format fmt; + + const struct dcmi_format **user_formats; + unsigned int num_user_formats; + const struct dcmi_format *current_fmt; + + /* Protect this data structure */ + struct mutex lock; + struct vb2_queue queue; + + struct v4l2_fwnode_bus_parallel bus; + struct completion complete; + struct clk *mclk; + enum state state; + struct dma_chan *dma_chan; + dma_cookie_t dma_cookie; + u32 misr; + int errors_count; + int buffers_count; +}; + +static inline struct stm32_dcmi *notifier_to_dcmi(struct v4l2_async_notifier *n) +{ + return container_of(n, struct stm32_dcmi, notifier); +} + +static inline u32 reg_read(void __iomem *base, u32 reg) +{ + return readl_relaxed(base + reg); +} + +static inline void reg_write(void __iomem *base, u32 reg, u32 val) +{ + writel_relaxed(val, base + reg); +} + +static inline void reg_set(void __iomem *base, u32 reg, u32 mask) +{ + reg_write(base, reg, reg_read(base, reg) | mask); +} + +static inline void reg_clear(void __iomem *base, u32 reg, u32 mask) +{ + reg_write(base, reg, reg_read(base, reg) & ~mask); +} + +static int dcmi_start_capture(struct stm32_dcmi *dcmi); + +static void dcmi_dma_callback(void *param) +{ + struct stm32_dcmi *dcmi = (struct stm32_dcmi *)param; + struct dma_chan *chan = dcmi->dma_chan; + struct dma_tx_state state; + enum dma_status status; + + spin_lock(&dcmi->irqlock); + + /* Check DMA status */ + status = dmaengine_tx_status(chan, dcmi->dma_cookie, &state); + + switch (status) { + case DMA_IN_PROGRESS: + dev_dbg(dcmi->dev, "%s: Received DMA_IN_PROGRESS\n", __func__); + break; + case DMA_PAUSED: + dev_err(dcmi->dev, "%s: Received DMA_PAUSED\n", __func__); + break; + case DMA_ERROR: + dev_err(dcmi->dev, "%s: Received DMA_ERROR\n", __func__); + break; + case DMA_COMPLETE: + dev_dbg(dcmi->dev, "%s: Received DMA_COMPLETE\n", __func__); + + if (dcmi->active) { + struct dcmi_buf *buf = dcmi->active; + struct vb2_v4l2_buffer *vbuf = &dcmi->active->vb; + + vbuf->sequence = dcmi->sequence++; + vbuf->field = V4L2_FIELD_NONE; + vbuf->vb2_buf.timestamp = ktime_get_ns(); + vb2_set_plane_payload(&vbuf->vb2_buf, 0, buf->size); + vb2_buffer_done(&vbuf->vb2_buf, VB2_BUF_STATE_DONE); + dev_dbg(dcmi->dev, "buffer[%d] done seq=%d\n", + vbuf->vb2_buf.index, vbuf->sequence); + + dcmi->buffers_count++; + dcmi->active = NULL; + } + + /* Restart a new DMA transfer with next buffer */ + if (dcmi->state == RUNNING) { + if (list_empty(&dcmi->buffers)) { + dev_err(dcmi->dev, "%s: No more buffer queued, cannot capture buffer", + __func__); + dcmi->errors_count++; + dcmi->active = NULL; + + spin_unlock(&dcmi->irqlock); + return; + } + + dcmi->active = list_entry(dcmi->buffers.next, + struct dcmi_buf, list); + + list_del_init(&dcmi->active->list); + + if (dcmi_start_capture(dcmi)) { + dev_err(dcmi->dev, "%s: Cannot restart capture on DMA complete", + __func__); + + spin_unlock(&dcmi->irqlock); + return; + } + + /* Enable capture */ + reg_set(dcmi->regs, DCMI_CR, CR_CAPTURE); + } + + break; + default: + dev_err(dcmi->dev, "%s: Received unknown status\n", __func__); + break; + } + + spin_unlock(&dcmi->irqlock); +} + +static int dcmi_start_dma(struct stm32_dcmi *dcmi, + struct dcmi_buf *buf) +{ + struct dma_async_tx_descriptor *desc = NULL; + struct dma_slave_config config; + int ret; + + memset(&config, 0, sizeof(config)); + + config.src_addr = (dma_addr_t)dcmi->res->start + DCMI_DR; + config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + config.dst_maxburst = 4; + + /* Configure DMA channel */ + ret = dmaengine_slave_config(dcmi->dma_chan, &config); + if (ret < 0) { + dev_err(dcmi->dev, "%s: DMA channel config failed (%d)\n", + __func__, ret); + return ret; + } + + /* Prepare a DMA transaction */ + desc = dmaengine_prep_slave_single(dcmi->dma_chan, buf->paddr, + buf->size, + DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT); + if (!desc) { + dev_err(dcmi->dev, "%s: DMA dmaengine_prep_slave_single failed for buffer size %zu\n", + __func__, buf->size); + return -EINVAL; + } + + /* Set completion callback routine for notification */ + desc->callback = dcmi_dma_callback; + desc->callback_param = dcmi; + + /* Push current DMA transaction in the pending queue */ + dcmi->dma_cookie = dmaengine_submit(desc); + + dma_async_issue_pending(dcmi->dma_chan); + + return 0; +} + +static int dcmi_start_capture(struct stm32_dcmi *dcmi) +{ + int ret; + struct dcmi_buf *buf = dcmi->active; + + if (!buf) + return -EINVAL; + + ret = dcmi_start_dma(dcmi, buf); + if (ret) { + dcmi->errors_count++; + return ret; + } + + /* Enable capture */ + reg_set(dcmi->regs, DCMI_CR, CR_CAPTURE); + + return 0; +} + +static irqreturn_t dcmi_irq_thread(int irq, void *arg) +{ + struct stm32_dcmi *dcmi = arg; + + spin_lock(&dcmi->irqlock); + + /* Stop capture is required */ + if (dcmi->state == STOPPING) { + reg_clear(dcmi->regs, DCMI_IER, IT_FRAME | IT_OVR | IT_ERR); + + dcmi->state = STOPPED; + + complete(&dcmi->complete); + + spin_unlock(&dcmi->irqlock); + return IRQ_HANDLED; + } + + if ((dcmi->misr & IT_OVR) || (dcmi->misr & IT_ERR)) { + /* + * An overflow or an error has been detected, + * stop current DMA transfert & restart it + */ + dev_warn(dcmi->dev, "%s: Overflow or error detected\n", + __func__); + + dcmi->errors_count++; + dmaengine_terminate_all(dcmi->dma_chan); + + reg_set(dcmi->regs, DCMI_ICR, IT_FRAME | IT_OVR | IT_ERR); + + dev_dbg(dcmi->dev, "Restarting capture after DCMI error\n"); + + if (dcmi_start_capture(dcmi)) { + dev_err(dcmi->dev, "%s: Cannot restart capture on overflow or error\n", + __func__); + + spin_unlock(&dcmi->irqlock); + return IRQ_HANDLED; + } + } + + spin_unlock(&dcmi->irqlock); + return IRQ_HANDLED; +} + +static irqreturn_t dcmi_irq_callback(int irq, void *arg) +{ + struct stm32_dcmi *dcmi = arg; + + spin_lock(&dcmi->irqlock); + + dcmi->misr = reg_read(dcmi->regs, DCMI_MIS); + + /* Clear interrupt */ + reg_set(dcmi->regs, DCMI_ICR, IT_FRAME | IT_OVR | IT_ERR); + + spin_unlock(&dcmi->irqlock); + + return IRQ_WAKE_THREAD; +} + +static int dcmi_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, + unsigned int *nplanes, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct stm32_dcmi *dcmi = vb2_get_drv_priv(vq); + unsigned int size; + + size = dcmi->fmt.fmt.pix.sizeimage; + + /* Make sure the image size is large enough */ + if (*nplanes) + return sizes[0] < size ? -EINVAL : 0; + + *nplanes = 1; + sizes[0] = size; + + dcmi->active = NULL; + + dev_dbg(dcmi->dev, "Setup queue, count=%d, size=%d\n", + *nbuffers, size); + + return 0; +} + +static int dcmi_buf_init(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct dcmi_buf *buf = container_of(vbuf, struct dcmi_buf, vb); + + INIT_LIST_HEAD(&buf->list); + + return 0; +} + +static int dcmi_buf_prepare(struct vb2_buffer *vb) +{ + struct stm32_dcmi *dcmi = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct dcmi_buf *buf = container_of(vbuf, struct dcmi_buf, vb); + unsigned long size; + + size = dcmi->fmt.fmt.pix.sizeimage; + + if (vb2_plane_size(vb, 0) < size) { + dev_err(dcmi->dev, "%s data will not fit into plane (%lu < %lu)\n", + __func__, vb2_plane_size(vb, 0), size); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, size); + + if (!buf->prepared) { + /* Get memory addresses */ + buf->paddr = + vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0); + buf->size = vb2_plane_size(&buf->vb.vb2_buf, 0); + buf->prepared = true; + + vb2_set_plane_payload(&buf->vb.vb2_buf, 0, buf->size); + + dev_dbg(dcmi->dev, "buffer[%d] phy=0x%pad size=%zu\n", + vb->index, &buf->paddr, buf->size); + } + + return 0; +} + +static void dcmi_buf_queue(struct vb2_buffer *vb) +{ + struct stm32_dcmi *dcmi = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct dcmi_buf *buf = container_of(vbuf, struct dcmi_buf, vb); + unsigned long flags = 0; + + spin_lock_irqsave(&dcmi->irqlock, flags); + + if ((dcmi->state == RUNNING) && (!dcmi->active)) { + dcmi->active = buf; + + dev_dbg(dcmi->dev, "Starting capture on buffer[%d] queued\n", + buf->vb.vb2_buf.index); + + if (dcmi_start_capture(dcmi)) { + dev_err(dcmi->dev, "%s: Cannot restart capture on overflow or error\n", + __func__); + + spin_unlock_irqrestore(&dcmi->irqlock, flags); + return; + } + } else { + /* Enqueue to video buffers list */ + list_add_tail(&buf->list, &dcmi->buffers); + } + + spin_unlock_irqrestore(&dcmi->irqlock, flags); +} + +static int dcmi_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct stm32_dcmi *dcmi = vb2_get_drv_priv(vq); + struct dcmi_buf *buf, *node; + u32 val; + int ret; + + ret = clk_enable(dcmi->mclk); + if (ret) { + dev_err(dcmi->dev, "%s: Failed to start streaming, cannot enable clock", + __func__); + goto err_release_buffers; + } + + /* Enable stream on the sub device */ + ret = v4l2_subdev_call(dcmi->entity.subdev, video, s_stream, 1); + if (ret && ret != -ENOIOCTLCMD) { + dev_err(dcmi->dev, "%s: Failed to start streaming, subdev streamon error", + __func__); + goto err_disable_clock; + } + + spin_lock_irq(&dcmi->irqlock); + + val = reg_read(dcmi->regs, DCMI_CR); + + val &= ~(CR_PCKPOL | CR_HSPOL | CR_VSPOL | + CR_EDM_0 | CR_EDM_1 | CR_FCRC_0 | + CR_FCRC_1 | CR_JPEG | CR_ESS); + + /* Set bus width */ + switch (dcmi->bus.bus_width) { + case 14: + val &= CR_EDM_0 + CR_EDM_1; + break; + case 12: + val &= CR_EDM_1; + break; + case 10: + val &= CR_EDM_0; + break; + default: + /* Set bus width to 8 bits by default */ + break; + } + + /* Set vertical synchronization polarity */ + if (dcmi->bus.flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) + val |= CR_VSPOL; + + /* Set horizontal synchronization polarity */ + if (dcmi->bus.flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) + val |= CR_HSPOL; + + /* Set pixel clock polarity */ + if (dcmi->bus.flags & V4L2_MBUS_PCLK_SAMPLE_RISING) + val |= CR_PCKPOL; + + reg_write(dcmi->regs, DCMI_CR, val); + + /* Enable dcmi */ + reg_set(dcmi->regs, DCMI_CR, CR_ENABLE); + + dcmi->state = RUNNING; + + dcmi->sequence = 0; + dcmi->errors_count = 0; + dcmi->buffers_count = 0; + dcmi->active = NULL; + + /* + * Start transfer if at least one buffer has been queued, + * otherwise transfer is deferred at buffer queueing + */ + if (list_empty(&dcmi->buffers)) { + dev_dbg(dcmi->dev, "Start streaming is deferred to next buffer queueing\n"); + spin_unlock_irq(&dcmi->irqlock); + return 0; + } + + dcmi->active = list_entry(dcmi->buffers.next, struct dcmi_buf, list); + list_del_init(&dcmi->active->list); + + dev_dbg(dcmi->dev, "Start streaming, starting capture\n"); + + ret = dcmi_start_capture(dcmi); + if (ret) { + dev_err(dcmi->dev, "%s: Start streaming failed, cannot start capture", + __func__); + + spin_unlock_irq(&dcmi->irqlock); + goto err_subdev_streamoff; + } + + /* Enable interruptions */ + reg_set(dcmi->regs, DCMI_IER, IT_FRAME | IT_OVR | IT_ERR); + + spin_unlock_irq(&dcmi->irqlock); + + return 0; + +err_subdev_streamoff: + v4l2_subdev_call(dcmi->entity.subdev, video, s_stream, 0); + +err_disable_clock: + clk_disable(dcmi->mclk); + +err_release_buffers: + spin_lock_irq(&dcmi->irqlock); + /* + * Return all buffers to vb2 in QUEUED state. + * This will give ownership back to userspace + */ + if (dcmi->active) { + buf = dcmi->active; + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_QUEUED); + dcmi->active = NULL; + } + list_for_each_entry_safe(buf, node, &dcmi->buffers, list) { + list_del_init(&buf->list); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_QUEUED); + } + spin_unlock_irq(&dcmi->irqlock); + + return ret; +} + +static void dcmi_stop_streaming(struct vb2_queue *vq) +{ + struct stm32_dcmi *dcmi = vb2_get_drv_priv(vq); + struct dcmi_buf *buf, *node; + unsigned long time_ms = msecs_to_jiffies(TIMEOUT_MS); + long timeout; + int ret; + + /* Disable stream on the sub device */ + ret = v4l2_subdev_call(dcmi->entity.subdev, video, s_stream, 0); + if (ret && ret != -ENOIOCTLCMD) + dev_err(dcmi->dev, "stream off failed in subdev\n"); + + dcmi->state = STOPPING; + + timeout = wait_for_completion_interruptible_timeout(&dcmi->complete, + time_ms); + + spin_lock_irq(&dcmi->irqlock); + + /* Disable interruptions */ + reg_clear(dcmi->regs, DCMI_IER, IT_FRAME | IT_OVR | IT_ERR); + + /* Disable DCMI */ + reg_clear(dcmi->regs, DCMI_CR, CR_ENABLE); + + if (!timeout) { + dev_err(dcmi->dev, "Timeout during stop streaming\n"); + dcmi->state = STOPPED; + } + + /* Return all queued buffers to vb2 in ERROR state */ + if (dcmi->active) { + buf = dcmi->active; + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + dcmi->active = NULL; + } + list_for_each_entry_safe(buf, node, &dcmi->buffers, list) { + list_del_init(&buf->list); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } + + spin_unlock_irq(&dcmi->irqlock); + + /* Stop all pending DMA operations */ + dmaengine_terminate_all(dcmi->dma_chan); + + clk_disable(dcmi->mclk); + + dev_dbg(dcmi->dev, "Stop streaming, errors=%d buffers=%d\n", + dcmi->errors_count, dcmi->buffers_count); +} + +static struct vb2_ops dcmi_video_qops = { + .queue_setup = dcmi_queue_setup, + .buf_init = dcmi_buf_init, + .buf_prepare = dcmi_buf_prepare, + .buf_queue = dcmi_buf_queue, + .start_streaming = dcmi_start_streaming, + .stop_streaming = dcmi_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int dcmi_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct stm32_dcmi *dcmi = video_drvdata(file); + + *fmt = dcmi->fmt; + + return 0; +} + +static const struct dcmi_format *find_format_by_fourcc(struct stm32_dcmi *dcmi, + unsigned int fourcc) +{ + unsigned int num_formats = dcmi->num_user_formats; + const struct dcmi_format *fmt; + unsigned int i; + + for (i = 0; i < num_formats; i++) { + fmt = dcmi->user_formats[i]; + if (fmt->fourcc == fourcc) + return fmt; + } + + return NULL; +} + +static int dcmi_try_fmt(struct stm32_dcmi *dcmi, struct v4l2_format *f, + const struct dcmi_format **current_fmt) +{ + const struct dcmi_format *dcmi_fmt; + struct v4l2_pix_format *pixfmt = &f->fmt.pix; + struct v4l2_subdev_pad_config pad_cfg; + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_TRY, + }; + int ret; + + dcmi_fmt = find_format_by_fourcc(dcmi, pixfmt->pixelformat); + if (!dcmi_fmt) { + dcmi_fmt = dcmi->user_formats[dcmi->num_user_formats - 1]; + pixfmt->pixelformat = dcmi_fmt->fourcc; + } + + /* Limit to hardware capabilities */ + pixfmt->width = clamp(pixfmt->width, MIN_WIDTH, MAX_WIDTH); + pixfmt->height = clamp(pixfmt->height, MIN_HEIGHT, MAX_HEIGHT); + + v4l2_fill_mbus_format(&format.format, pixfmt, dcmi_fmt->mbus_code); + ret = v4l2_subdev_call(dcmi->entity.subdev, pad, set_fmt, + &pad_cfg, &format); + if (ret < 0) + return ret; + + v4l2_fill_pix_format(pixfmt, &format.format); + + pixfmt->field = V4L2_FIELD_NONE; + pixfmt->bytesperline = pixfmt->width * dcmi_fmt->bpp; + pixfmt->sizeimage = pixfmt->bytesperline * pixfmt->height; + + if (current_fmt) + *current_fmt = dcmi_fmt; + + return 0; +} + +static int dcmi_set_fmt(struct stm32_dcmi *dcmi, struct v4l2_format *f) +{ + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + const struct dcmi_format *current_fmt; + int ret; + + ret = dcmi_try_fmt(dcmi, f, ¤t_fmt); + if (ret) + return ret; + + v4l2_fill_mbus_format(&format.format, &f->fmt.pix, + current_fmt->mbus_code); + ret = v4l2_subdev_call(dcmi->entity.subdev, pad, + set_fmt, NULL, &format); + if (ret < 0) + return ret; + + dcmi->fmt = *f; + dcmi->current_fmt = current_fmt; + + return 0; +} + +static int dcmi_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct stm32_dcmi *dcmi = video_drvdata(file); + + if (vb2_is_streaming(&dcmi->queue)) + return -EBUSY; + + return dcmi_set_fmt(dcmi, f); +} + +static int dcmi_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct stm32_dcmi *dcmi = video_drvdata(file); + + return dcmi_try_fmt(dcmi, f, NULL); +} + +static int dcmi_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct stm32_dcmi *dcmi = video_drvdata(file); + + if (f->index >= dcmi->num_user_formats) + return -EINVAL; + + f->pixelformat = dcmi->user_formats[f->index]->fourcc; + return 0; +} + +static int dcmi_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strlcpy(cap->driver, DRV_NAME, sizeof(cap->driver)); + strlcpy(cap->card, "STM32 Camera Memory Interface", + sizeof(cap->card)); + strlcpy(cap->bus_info, "platform:dcmi", sizeof(cap->bus_info)); + return 0; +} + +static int dcmi_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + if (i->index != 0) + return -EINVAL; + + i->type = V4L2_INPUT_TYPE_CAMERA; + strlcpy(i->name, "Camera", sizeof(i->name)); + return 0; +} + +static int dcmi_g_input(struct file *file, void *priv, unsigned int *i) +{ + *i = 0; + return 0; +} + +static int dcmi_s_input(struct file *file, void *priv, unsigned int i) +{ + if (i > 0) + return -EINVAL; + return 0; +} + +static int dcmi_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + struct stm32_dcmi *dcmi = video_drvdata(file); + const struct dcmi_format *dcmi_fmt; + struct v4l2_subdev_frame_size_enum fse = { + .index = fsize->index, + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + int ret; + + dcmi_fmt = find_format_by_fourcc(dcmi, fsize->pixel_format); + if (!dcmi_fmt) + return -EINVAL; + + fse.code = dcmi_fmt->mbus_code; + + ret = v4l2_subdev_call(dcmi->entity.subdev, pad, enum_frame_size, + NULL, &fse); + if (ret) + return ret; + + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + fsize->discrete.width = fse.max_width; + fsize->discrete.height = fse.max_height; + + return 0; +} + +static int dcmi_enum_frameintervals(struct file *file, void *fh, + struct v4l2_frmivalenum *fival) +{ + struct stm32_dcmi *dcmi = video_drvdata(file); + const struct dcmi_format *dcmi_fmt; + struct v4l2_subdev_frame_interval_enum fie = { + .index = fival->index, + .width = fival->width, + .height = fival->height, + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + int ret; + + dcmi_fmt = find_format_by_fourcc(dcmi, fival->pixel_format); + if (!dcmi_fmt) + return -EINVAL; + + fie.code = dcmi_fmt->mbus_code; + + ret = v4l2_subdev_call(dcmi->entity.subdev, pad, + enum_frame_interval, NULL, &fie); + if (ret) + return ret; + + fival->type = V4L2_FRMIVAL_TYPE_DISCRETE; + fival->discrete = fie.interval; + + return 0; +} + +static const struct of_device_id stm32_dcmi_of_match[] = { + { .compatible = "st,stm32-dcmi"}, + { /* end node */ }, +}; +MODULE_DEVICE_TABLE(of, stm32_dcmi_of_match); + +static int dcmi_open(struct file *file) +{ + struct stm32_dcmi *dcmi = video_drvdata(file); + struct v4l2_subdev *sd = dcmi->entity.subdev; + int ret; + + if (mutex_lock_interruptible(&dcmi->lock)) + return -ERESTARTSYS; + + ret = v4l2_fh_open(file); + if (ret < 0) + goto unlock; + + if (!v4l2_fh_is_singular_file(file)) + goto fh_rel; + + ret = v4l2_subdev_call(sd, core, s_power, 1); + if (ret < 0 && ret != -ENOIOCTLCMD) + goto fh_rel; + + ret = dcmi_set_fmt(dcmi, &dcmi->fmt); + if (ret) + v4l2_subdev_call(sd, core, s_power, 0); +fh_rel: + if (ret) + v4l2_fh_release(file); +unlock: + mutex_unlock(&dcmi->lock); + return ret; +} + +static int dcmi_release(struct file *file) +{ + struct stm32_dcmi *dcmi = video_drvdata(file); + struct v4l2_subdev *sd = dcmi->entity.subdev; + bool fh_singular; + int ret; + + mutex_lock(&dcmi->lock); + + fh_singular = v4l2_fh_is_singular_file(file); + + ret = _vb2_fop_release(file, NULL); + + if (fh_singular) + v4l2_subdev_call(sd, core, s_power, 0); + + mutex_unlock(&dcmi->lock); + + return ret; +} + +static const struct v4l2_ioctl_ops dcmi_ioctl_ops = { + .vidioc_querycap = dcmi_querycap, + + .vidioc_try_fmt_vid_cap = dcmi_try_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = dcmi_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = dcmi_s_fmt_vid_cap, + .vidioc_enum_fmt_vid_cap = dcmi_enum_fmt_vid_cap, + + .vidioc_enum_input = dcmi_enum_input, + .vidioc_g_input = dcmi_g_input, + .vidioc_s_input = dcmi_s_input, + + .vidioc_enum_framesizes = dcmi_enum_framesizes, + .vidioc_enum_frameintervals = dcmi_enum_frameintervals, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static const struct v4l2_file_operations dcmi_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = video_ioctl2, + .open = dcmi_open, + .release = dcmi_release, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, +#ifndef CONFIG_MMU + .get_unmapped_area = vb2_fop_get_unmapped_area, +#endif + .read = vb2_fop_read, +}; + +static int dcmi_set_default_fmt(struct stm32_dcmi *dcmi) +{ + struct v4l2_format f = { + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .fmt.pix = { + .width = CIF_WIDTH, + .height = CIF_HEIGHT, + .field = V4L2_FIELD_NONE, + .pixelformat = dcmi->user_formats[0]->fourcc, + }, + }; + int ret; + + ret = dcmi_try_fmt(dcmi, &f, NULL); + if (ret) + return ret; + dcmi->current_fmt = dcmi->user_formats[0]; + dcmi->fmt = f; + return 0; +} + +static const struct dcmi_format dcmi_formats[] = { + { + .fourcc = V4L2_PIX_FMT_RGB565, + .mbus_code = MEDIA_BUS_FMT_RGB565_2X8_LE, + .bpp = 2, + }, { + .fourcc = V4L2_PIX_FMT_YUYV, + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, + .bpp = 2, + }, { + .fourcc = V4L2_PIX_FMT_UYVY, + .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8, + .bpp = 2, + }, +}; + +static int dcmi_formats_init(struct stm32_dcmi *dcmi) +{ + const struct dcmi_format *dcmi_fmts[ARRAY_SIZE(dcmi_formats)]; + unsigned int num_fmts = 0, i, j; + struct v4l2_subdev *subdev = dcmi->entity.subdev; + struct v4l2_subdev_mbus_code_enum mbus_code = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + + while (!v4l2_subdev_call(subdev, pad, enum_mbus_code, + NULL, &mbus_code)) { + for (i = 0; i < ARRAY_SIZE(dcmi_formats); i++) { + if (dcmi_formats[i].mbus_code != mbus_code.code) + continue; + + /* Code supported, have we got this fourcc yet? */ + for (j = 0; j < num_fmts; j++) + if (dcmi_fmts[j]->fourcc == + dcmi_formats[i].fourcc) + /* Already available */ + break; + if (j == num_fmts) + /* New */ + dcmi_fmts[num_fmts++] = dcmi_formats + i; + } + mbus_code.index++; + } + + if (!num_fmts) + return -ENXIO; + + dcmi->num_user_formats = num_fmts; + dcmi->user_formats = devm_kcalloc(dcmi->dev, + num_fmts, sizeof(struct dcmi_format *), + GFP_KERNEL); + if (!dcmi->user_formats) { + dev_err(dcmi->dev, "could not allocate memory\n"); + return -ENOMEM; + } + + memcpy(dcmi->user_formats, dcmi_fmts, + num_fmts * sizeof(struct dcmi_format *)); + dcmi->current_fmt = dcmi->user_formats[0]; + + return 0; +} + +static int dcmi_graph_notify_complete(struct v4l2_async_notifier *notifier) +{ + struct stm32_dcmi *dcmi = notifier_to_dcmi(notifier); + int ret; + + dcmi->vdev->ctrl_handler = dcmi->entity.subdev->ctrl_handler; + ret = dcmi_formats_init(dcmi); + if (ret) { + dev_err(dcmi->dev, "No supported mediabus format found\n"); + return ret; + } + + ret = dcmi_set_default_fmt(dcmi); + if (ret) { + dev_err(dcmi->dev, "Could not set default format\n"); + return ret; + } + + ret = video_register_device(dcmi->vdev, VFL_TYPE_GRABBER, -1); + if (ret) { + dev_err(dcmi->dev, "Failed to register video device\n"); + return ret; + } + + dev_dbg(dcmi->dev, "Device registered as %s\n", + video_device_node_name(dcmi->vdev)); + return 0; +} + +static void dcmi_graph_notify_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_subdev *asd) +{ + struct stm32_dcmi *dcmi = notifier_to_dcmi(notifier); + + dev_dbg(dcmi->dev, "Removing %s\n", video_device_node_name(dcmi->vdev)); + + /* Checks internaly if vdev has been init or not */ + video_unregister_device(dcmi->vdev); +} + +static int dcmi_graph_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct stm32_dcmi *dcmi = notifier_to_dcmi(notifier); + + dev_dbg(dcmi->dev, "Subdev %s bound\n", subdev->name); + + dcmi->entity.subdev = subdev; + + return 0; +} + +static int dcmi_graph_parse(struct stm32_dcmi *dcmi, struct device_node *node) +{ + struct device_node *ep = NULL; + struct device_node *remote; + + while (1) { + ep = of_graph_get_next_endpoint(node, ep); + if (!ep) + return -EINVAL; + + remote = of_graph_get_remote_port_parent(ep); + if (!remote) { + of_node_put(ep); + return -EINVAL; + } + + /* Remote node to connect */ + dcmi->entity.node = remote; + dcmi->entity.asd.match_type = V4L2_ASYNC_MATCH_FWNODE; + dcmi->entity.asd.match.fwnode.fwnode = of_fwnode_handle(remote); + return 0; + } +} + +static int dcmi_graph_init(struct stm32_dcmi *dcmi) +{ + struct v4l2_async_subdev **subdevs = NULL; + int ret; + + /* Parse the graph to extract a list of subdevice DT nodes. */ + ret = dcmi_graph_parse(dcmi, dcmi->dev->of_node); + if (ret < 0) { + dev_err(dcmi->dev, "Graph parsing failed\n"); + return ret; + } + + /* Register the subdevices notifier. */ + subdevs = devm_kzalloc(dcmi->dev, sizeof(*subdevs), GFP_KERNEL); + if (!subdevs) { + of_node_put(dcmi->entity.node); + return -ENOMEM; + } + + subdevs[0] = &dcmi->entity.asd; + + dcmi->notifier.subdevs = subdevs; + dcmi->notifier.num_subdevs = 1; + dcmi->notifier.bound = dcmi_graph_notify_bound; + dcmi->notifier.unbind = dcmi_graph_notify_unbind; + dcmi->notifier.complete = dcmi_graph_notify_complete; + + ret = v4l2_async_notifier_register(&dcmi->v4l2_dev, &dcmi->notifier); + if (ret < 0) { + dev_err(dcmi->dev, "Notifier registration failed\n"); + of_node_put(dcmi->entity.node); + return ret; + } + + return 0; +} + +static int dcmi_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + const struct of_device_id *match = NULL; + struct v4l2_fwnode_endpoint ep; + struct stm32_dcmi *dcmi; + struct vb2_queue *q; + struct dma_chan *chan; + struct clk *mclk; + int irq; + int ret = 0; + + match = of_match_device(of_match_ptr(stm32_dcmi_of_match), &pdev->dev); + if (!match) { + dev_err(&pdev->dev, "Could not find a match in devicetree\n"); + return -ENODEV; + } + + dcmi = devm_kzalloc(&pdev->dev, sizeof(struct stm32_dcmi), GFP_KERNEL); + if (!dcmi) + return -ENOMEM; + + dcmi->rstc = devm_reset_control_get(&pdev->dev, NULL); + if (IS_ERR(dcmi->rstc)) { + dev_err(&pdev->dev, "Could not get reset control\n"); + return -ENODEV; + } + + /* Get bus characteristics from devicetree */ + np = of_graph_get_next_endpoint(np, NULL); + if (!np) { + dev_err(&pdev->dev, "Could not find the endpoint\n"); + of_node_put(np); + return -ENODEV; + } + + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(np), &ep); + if (ret) { + dev_err(&pdev->dev, "Could not parse the endpoint\n"); + of_node_put(np); + return -ENODEV; + } + + if (ep.bus_type == V4L2_MBUS_CSI2) { + dev_err(&pdev->dev, "CSI bus not supported\n"); + of_node_put(np); + return -ENODEV; + } + dcmi->bus.flags = ep.bus.parallel.flags; + dcmi->bus.bus_width = ep.bus.parallel.bus_width; + dcmi->bus.data_shift = ep.bus.parallel.data_shift; + + of_node_put(np); + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) { + dev_err(&pdev->dev, "Could not get irq\n"); + return -ENODEV; + } + + dcmi->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!dcmi->res) { + dev_err(&pdev->dev, "Could not get resource\n"); + return -ENODEV; + } + + dcmi->regs = devm_ioremap_resource(&pdev->dev, dcmi->res); + if (IS_ERR(dcmi->regs)) { + dev_err(&pdev->dev, "Could not map registers\n"); + return PTR_ERR(dcmi->regs); + } + + ret = devm_request_threaded_irq(&pdev->dev, irq, dcmi_irq_callback, + dcmi_irq_thread, IRQF_ONESHOT, + dev_name(&pdev->dev), dcmi); + if (ret) { + dev_err(&pdev->dev, "Unable to request irq %d\n", irq); + return -ENODEV; + } + + mclk = devm_clk_get(&pdev->dev, "mclk"); + if (IS_ERR(mclk)) { + dev_err(&pdev->dev, "Unable to get mclk\n"); + return PTR_ERR(mclk); + } + + chan = dma_request_slave_channel(&pdev->dev, "tx"); + if (!chan) { + dev_info(&pdev->dev, "Unable to request DMA channel, defer probing\n"); + return -EPROBE_DEFER; + } + + ret = clk_prepare(mclk); + if (ret) { + dev_err(&pdev->dev, "Unable to prepare mclk %p\n", mclk); + goto err_dma_release; + } + + spin_lock_init(&dcmi->irqlock); + mutex_init(&dcmi->lock); + init_completion(&dcmi->complete); + INIT_LIST_HEAD(&dcmi->buffers); + + dcmi->dev = &pdev->dev; + dcmi->mclk = mclk; + dcmi->state = STOPPED; + dcmi->dma_chan = chan; + + q = &dcmi->queue; + + /* Initialize the top-level structure */ + ret = v4l2_device_register(&pdev->dev, &dcmi->v4l2_dev); + if (ret) + goto err_clk_unprepare; + + dcmi->vdev = video_device_alloc(); + if (!dcmi->vdev) { + ret = -ENOMEM; + goto err_device_unregister; + } + + /* Video node */ + dcmi->vdev->fops = &dcmi_fops; + dcmi->vdev->v4l2_dev = &dcmi->v4l2_dev; + dcmi->vdev->queue = &dcmi->queue; + strlcpy(dcmi->vdev->name, KBUILD_MODNAME, sizeof(dcmi->vdev->name)); + dcmi->vdev->release = video_device_release; + dcmi->vdev->ioctl_ops = &dcmi_ioctl_ops; + dcmi->vdev->lock = &dcmi->lock; + dcmi->vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | + V4L2_CAP_READWRITE; + video_set_drvdata(dcmi->vdev, dcmi); + + /* Buffer queue */ + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_READ | VB2_DMABUF; + q->lock = &dcmi->lock; + q->drv_priv = dcmi; + q->buf_struct_size = sizeof(struct dcmi_buf); + q->ops = &dcmi_video_qops; + q->mem_ops = &vb2_dma_contig_memops; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->min_buffers_needed = 2; + q->dev = &pdev->dev; + + ret = vb2_queue_init(q); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to initialize vb2 queue\n"); + goto err_device_release; + } + + ret = dcmi_graph_init(dcmi); + if (ret < 0) + goto err_device_release; + + /* Reset device */ + ret = reset_control_assert(dcmi->rstc); + if (ret) { + dev_err(&pdev->dev, "Failed to assert the reset line\n"); + goto err_device_release; + } + + usleep_range(3000, 5000); + + ret = reset_control_deassert(dcmi->rstc); + if (ret) { + dev_err(&pdev->dev, "Failed to deassert the reset line\n"); + goto err_device_release; + } + + dev_info(&pdev->dev, "Probe done\n"); + + platform_set_drvdata(pdev, dcmi); + return 0; + +err_device_release: + video_device_release(dcmi->vdev); +err_device_unregister: + v4l2_device_unregister(&dcmi->v4l2_dev); +err_clk_unprepare: + clk_unprepare(dcmi->mclk); +err_dma_release: + dma_release_channel(dcmi->dma_chan); + + return ret; +} + +static int dcmi_remove(struct platform_device *pdev) +{ + struct stm32_dcmi *dcmi = platform_get_drvdata(pdev); + + v4l2_async_notifier_unregister(&dcmi->notifier); + v4l2_device_unregister(&dcmi->v4l2_dev); + clk_unprepare(dcmi->mclk); + dma_release_channel(dcmi->dma_chan); + + return 0; +} + +static struct platform_driver stm32_dcmi_driver = { + .probe = dcmi_probe, + .remove = dcmi_remove, + .driver = { + .name = DRV_NAME, + .of_match_table = of_match_ptr(stm32_dcmi_of_match), + }, +}; + +module_platform_driver(stm32_dcmi_driver); + +MODULE_AUTHOR("Yannick Fertre <yannick.fertre@st.com>"); +MODULE_AUTHOR("Hugues Fruchet <hugues.fruchet@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics STM32 Digital Camera Memory Interface driver"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("video"); diff --git a/drivers/media/platform/ti-vpe/cal.c b/drivers/media/platform/ti-vpe/cal.c index 7a058b6e03d0..177faa36bc16 100644 --- a/drivers/media/platform/ti-vpe/cal.c +++ b/drivers/media/platform/ti-vpe/cal.c @@ -21,7 +21,7 @@ #include <linux/of_device.h> #include <linux/of_graph.h> -#include <media/v4l2-of.h> +#include <media/v4l2-fwnode.h> #include <media/v4l2-async.h> #include <media/v4l2-common.h> #include <media/v4l2-ctrls.h> @@ -270,7 +270,7 @@ struct cal_ctx { struct video_device vdev; struct v4l2_async_notifier notifier; struct v4l2_subdev *sensor; - struct v4l2_of_endpoint endpoint; + struct v4l2_fwnode_endpoint endpoint; struct v4l2_async_subdev asd; struct v4l2_async_subdev *asd_list[1]; @@ -608,7 +608,8 @@ static void csi2_lane_config(struct cal_ctx *ctx) u32 val = reg_read(ctx->dev, CAL_CSI2_COMPLEXIO_CFG(ctx->csi2_port)); u32 lane_mask = CAL_CSI2_COMPLEXIO_CFG_CLOCK_POSITION_MASK; u32 polarity_mask = CAL_CSI2_COMPLEXIO_CFG_CLOCK_POL_MASK; - struct v4l2_of_bus_mipi_csi2 *mipi_csi2 = &ctx->endpoint.bus.mipi_csi2; + struct v4l2_fwnode_bus_mipi_csi2 *mipi_csi2 = + &ctx->endpoint.bus.mipi_csi2; int lane; set_field(&val, mipi_csi2->clock_lane + 1, lane_mask); @@ -1643,7 +1644,7 @@ static int of_cal_create_instance(struct cal_ctx *ctx, int inst) struct platform_device *pdev = ctx->dev->pdev; struct device_node *ep_node, *port, *remote_ep, *sensor_node, *parent; - struct v4l2_of_endpoint *endpoint; + struct v4l2_fwnode_endpoint *endpoint; struct v4l2_async_subdev *asd; u32 regval = 0; int ret, index, found_port = 0, lane; @@ -1698,15 +1699,15 @@ static int of_cal_create_instance(struct cal_ctx *ctx, int inst) ctx_dbg(3, ctx, "can't get remote parent\n"); goto cleanup_exit; } - asd->match_type = V4L2_ASYNC_MATCH_OF; - asd->match.of.node = sensor_node; + asd->match_type = V4L2_ASYNC_MATCH_FWNODE; + asd->match.fwnode.fwnode = of_fwnode_handle(sensor_node); remote_ep = of_parse_phandle(ep_node, "remote-endpoint", 0); if (!remote_ep) { ctx_dbg(3, ctx, "can't get remote-endpoint\n"); goto cleanup_exit; } - v4l2_of_parse_endpoint(remote_ep, endpoint); + v4l2_fwnode_endpoint_parse(of_fwnode_handle(remote_ep), endpoint); if (endpoint->bus_type != V4L2_MBUS_CSI2) { ctx_err(ctx, "Port:%d sub-device %s is not a CSI2 device\n", diff --git a/drivers/media/platform/video-mux.c b/drivers/media/platform/video-mux.c new file mode 100644 index 000000000000..665744716f73 --- /dev/null +++ b/drivers/media/platform/video-mux.c @@ -0,0 +1,334 @@ +/* + * video stream multiplexer controlled via mux control + * + * Copyright (C) 2013 Pengutronix, Sascha Hauer <kernel@pengutronix.de> + * Copyright (C) 2016-2017 Pengutronix, Philipp Zabel <kernel@pengutronix.de> + * + * 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; either version 2 + * of the License, or (at your option) any later version. + * 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. + */ + +#include <linux/err.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/regmap.h> +#include <linux/mfd/syscon.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> +#include <media/v4l2-async.h> +#include <media/v4l2-device.h> +#include <media/v4l2-subdev.h> + +struct video_mux { + struct v4l2_subdev subdev; + struct media_pad *pads; + struct v4l2_mbus_framefmt *format_mbus; + struct regmap_field *field; + struct mutex lock; + int active; +}; + +static inline struct video_mux *v4l2_subdev_to_video_mux(struct v4l2_subdev *sd) +{ + return container_of(sd, struct video_mux, subdev); +} + +static int video_mux_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); + struct video_mux *vmux = v4l2_subdev_to_video_mux(sd); + int ret = 0; + + /* + * The mux state is determined by the enabled sink pad link. + * Enabling or disabling the source pad link has no effect. + */ + if (local->flags & MEDIA_PAD_FL_SOURCE) + return 0; + + dev_dbg(sd->dev, "link setup '%s':%d->'%s':%d[%d]", + remote->entity->name, remote->index, local->entity->name, + local->index, flags & MEDIA_LNK_FL_ENABLED); + + mutex_lock(&vmux->lock); + + if (flags & MEDIA_LNK_FL_ENABLED) { + if (vmux->active == local->index) + goto out; + + if (vmux->active >= 0) { + ret = -EBUSY; + goto out; + } + + dev_dbg(sd->dev, "setting %d active\n", local->index); + ret = regmap_field_write(vmux->field, local->index); + if (ret < 0) + goto out; + vmux->active = local->index; + } else { + if (vmux->active != local->index) + goto out; + + dev_dbg(sd->dev, "going inactive\n"); + vmux->active = -1; + } + +out: + mutex_unlock(&vmux->lock); + return ret; +} + +static const struct media_entity_operations video_mux_ops = { + .link_setup = video_mux_link_setup, + .link_validate = v4l2_subdev_link_validate, +}; + +static int video_mux_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct video_mux *vmux = v4l2_subdev_to_video_mux(sd); + struct v4l2_subdev *upstream_sd; + struct media_pad *pad; + + if (vmux->active == -1) { + dev_err(sd->dev, "Can not start streaming on inactive mux\n"); + return -EINVAL; + } + + pad = media_entity_remote_pad(&sd->entity.pads[vmux->active]); + if (!pad) { + dev_err(sd->dev, "Failed to find remote source pad\n"); + return -ENOLINK; + } + + if (!is_media_entity_v4l2_subdev(pad->entity)) { + dev_err(sd->dev, "Upstream entity is not a v4l2 subdev\n"); + return -ENODEV; + } + + upstream_sd = media_entity_to_v4l2_subdev(pad->entity); + + return v4l2_subdev_call(upstream_sd, video, s_stream, enable); +} + +static const struct v4l2_subdev_video_ops video_mux_subdev_video_ops = { + .s_stream = video_mux_s_stream, +}; + +static struct v4l2_mbus_framefmt * +__video_mux_get_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + unsigned int pad, u32 which) +{ + struct video_mux *vmux = v4l2_subdev_to_video_mux(sd); + + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_format(sd, cfg, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &vmux->format_mbus[pad]; + default: + return NULL; + } +} + +static int video_mux_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *sdformat) +{ + struct video_mux *vmux = v4l2_subdev_to_video_mux(sd); + + mutex_lock(&vmux->lock); + + sdformat->format = *__video_mux_get_pad_format(sd, cfg, sdformat->pad, + sdformat->which); + + mutex_unlock(&vmux->lock); + + return 0; +} + +static int video_mux_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *sdformat) +{ + struct video_mux *vmux = v4l2_subdev_to_video_mux(sd); + struct v4l2_mbus_framefmt *mbusformat; + struct media_pad *pad = &vmux->pads[sdformat->pad]; + + mbusformat = __video_mux_get_pad_format(sd, cfg, sdformat->pad, + sdformat->which); + if (!mbusformat) + return -EINVAL; + + mutex_lock(&vmux->lock); + + /* Source pad mirrors active sink pad, no limitations on sink pads */ + if ((pad->flags & MEDIA_PAD_FL_SOURCE) && vmux->active >= 0) + sdformat->format = vmux->format_mbus[vmux->active]; + + *mbusformat = sdformat->format; + + mutex_unlock(&vmux->lock); + + return 0; +} + +static const struct v4l2_subdev_pad_ops video_mux_pad_ops = { + .get_fmt = video_mux_get_format, + .set_fmt = video_mux_set_format, +}; + +static const struct v4l2_subdev_ops video_mux_subdev_ops = { + .pad = &video_mux_pad_ops, + .video = &video_mux_subdev_video_ops, +}; + +static int video_mux_probe_mmio_mux(struct video_mux *vmux) +{ + struct device *dev = vmux->subdev.dev; + struct of_phandle_args args; + struct reg_field field; + struct regmap *regmap; + u32 reg, mask; + int ret; + + ret = of_parse_phandle_with_args(dev->of_node, "mux-controls", + "#mux-control-cells", 0, &args); + if (ret) + return ret; + + if (!of_device_is_compatible(args.np, "mmio-mux")) + return -EINVAL; + + regmap = syscon_node_to_regmap(args.np->parent); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + ret = of_property_read_u32_index(args.np, "mux-reg-masks", + 2 * args.args[0], ®); + if (!ret) + ret = of_property_read_u32_index(args.np, "mux-reg-masks", + 2 * args.args[0] + 1, &mask); + if (ret < 0) + return ret; + + field.reg = reg; + field.msb = fls(mask) - 1; + field.lsb = ffs(mask) - 1; + + vmux->field = devm_regmap_field_alloc(dev, regmap, field); + if (IS_ERR(vmux->field)) + return PTR_ERR(vmux->field); + + return 0; +} + +static int video_mux_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct device_node *ep; + struct video_mux *vmux; + unsigned int num_pads = 0; + int ret; + int i; + + vmux = devm_kzalloc(dev, sizeof(*vmux), GFP_KERNEL); + if (!vmux) + return -ENOMEM; + + platform_set_drvdata(pdev, vmux); + + v4l2_subdev_init(&vmux->subdev, &video_mux_subdev_ops); + snprintf(vmux->subdev.name, sizeof(vmux->subdev.name), "%s", np->name); + vmux->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + vmux->subdev.dev = dev; + + /* + * The largest numbered port is the output port. It determines + * total number of pads. + */ + for_each_endpoint_of_node(np, ep) { + struct of_endpoint endpoint; + + of_graph_parse_endpoint(ep, &endpoint); + num_pads = max(num_pads, endpoint.port + 1); + } + + if (num_pads < 2) { + dev_err(dev, "Not enough ports %d\n", num_pads); + return -EINVAL; + } + + ret = video_mux_probe_mmio_mux(vmux); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to get mux: %d\n", ret); + return ret; + } + + mutex_init(&vmux->lock); + vmux->active = -1; + vmux->pads = devm_kcalloc(dev, num_pads, sizeof(*vmux->pads), + GFP_KERNEL); + vmux->format_mbus = devm_kcalloc(dev, num_pads, + sizeof(*vmux->format_mbus), + GFP_KERNEL); + + for (i = 0; i < num_pads - 1; i++) + vmux->pads[i].flags = MEDIA_PAD_FL_SINK; + vmux->pads[num_pads - 1].flags = MEDIA_PAD_FL_SOURCE; + + vmux->subdev.entity.function = MEDIA_ENT_F_VID_MUX; + ret = media_entity_pads_init(&vmux->subdev.entity, num_pads, + vmux->pads); + if (ret < 0) + return ret; + + vmux->subdev.entity.ops = &video_mux_ops; + + return v4l2_async_register_subdev(&vmux->subdev); +} + +static int video_mux_remove(struct platform_device *pdev) +{ + struct video_mux *vmux = platform_get_drvdata(pdev); + struct v4l2_subdev *sd = &vmux->subdev; + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + + return 0; +} + +static const struct of_device_id video_mux_dt_ids[] = { + { .compatible = "video-mux", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, video_mux_dt_ids); + +static struct platform_driver video_mux_driver = { + .probe = video_mux_probe, + .remove = video_mux_remove, + .driver = { + .of_match_table = video_mux_dt_ids, + .name = "video-mux", + }, +}; + +module_platform_driver(video_mux_driver); + +MODULE_DESCRIPTION("video stream multiplexer"); +MODULE_AUTHOR("Sascha Hauer, Pengutronix"); +MODULE_AUTHOR("Philipp Zabel, Pengutronix"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/vivid/vivid-cec.c b/drivers/media/platform/vivid/vivid-cec.c index 653f4099f737..e15705758969 100644 --- a/drivers/media/platform/vivid/vivid-cec.c +++ b/drivers/media/platform/vivid/vivid-cec.c @@ -34,7 +34,7 @@ void vivid_cec_bus_free_work(struct vivid_dev *dev) cancel_delayed_work_sync(&cw->work); spin_lock(&dev->cec_slock); list_del(&cw->list); - cec_transmit_done(cw->adap, CEC_TX_STATUS_LOW_DRIVE, 0, 0, 1, 0); + cec_transmit_attempt_done(cw->adap, CEC_TX_STATUS_LOW_DRIVE); kfree(cw); } spin_unlock(&dev->cec_slock); @@ -84,7 +84,7 @@ static void vivid_cec_xfer_done_worker(struct work_struct *work) dev->cec_xfer_start_jiffies = 0; list_del(&cw->list); spin_unlock(&dev->cec_slock); - cec_transmit_done(cw->adap, cw->tx_status, 0, valid_dest ? 0 : 1, 0, 0); + cec_transmit_attempt_done(cw->adap, cw->tx_status); /* Broadcast message */ if (adap != dev->cec_rx_adap) @@ -105,7 +105,7 @@ static void vivid_cec_xfer_try_worker(struct work_struct *work) if (dev->cec_xfer_time_jiffies) { list_del(&cw->list); spin_unlock(&dev->cec_slock); - cec_transmit_done(cw->adap, CEC_TX_STATUS_ARB_LOST, 1, 0, 0, 0); + cec_transmit_attempt_done(cw->adap, CEC_TX_STATUS_ARB_LOST); kfree(cw); } else { INIT_DELAYED_WORK(&cw->work, vivid_cec_xfer_done_worker); diff --git a/drivers/media/platform/xilinx/Kconfig b/drivers/media/platform/xilinx/Kconfig index 84bae795b70d..a5d21b7c6e0b 100644 --- a/drivers/media/platform/xilinx/Kconfig +++ b/drivers/media/platform/xilinx/Kconfig @@ -2,6 +2,7 @@ config VIDEO_XILINX tristate "Xilinx Video IP (EXPERIMENTAL)" depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API && OF && HAS_DMA select VIDEOBUF2_DMA_CONTIG + select V4L2_FWNODE ---help--- Driver for Xilinx Video IP Pipelines diff --git a/drivers/media/platform/xilinx/xilinx-vipp.c b/drivers/media/platform/xilinx/xilinx-vipp.c index feb3b2f1d874..ac4704388920 100644 --- a/drivers/media/platform/xilinx/xilinx-vipp.c +++ b/drivers/media/platform/xilinx/xilinx-vipp.c @@ -22,7 +22,7 @@ #include <media/v4l2-async.h> #include <media/v4l2-common.h> #include <media/v4l2-device.h> -#include <media/v4l2-of.h> +#include <media/v4l2-fwnode.h> #include "xilinx-dma.h" #include "xilinx-vipp.h" @@ -74,7 +74,7 @@ static int xvip_graph_build_one(struct xvip_composite_device *xdev, struct media_pad *local_pad; struct media_pad *remote_pad; struct xvip_graph_entity *ent; - struct v4l2_of_link link; + struct v4l2_fwnode_link link; struct device_node *ep = NULL; struct device_node *next; int ret = 0; @@ -92,7 +92,7 @@ static int xvip_graph_build_one(struct xvip_composite_device *xdev, dev_dbg(xdev->dev, "processing endpoint %s\n", ep->full_name); - ret = v4l2_of_parse_link(ep, &link); + ret = v4l2_fwnode_parse_link(of_fwnode_handle(ep), &link); if (ret < 0) { dev_err(xdev->dev, "failed to parse link for %s\n", ep->full_name); @@ -103,9 +103,10 @@ static int xvip_graph_build_one(struct xvip_composite_device *xdev, * the link. */ if (link.local_port >= local->num_pads) { - dev_err(xdev->dev, "invalid port number %u on %s\n", - link.local_port, link.local_node->full_name); - v4l2_of_put_link(&link); + dev_err(xdev->dev, "invalid port number %u for %s\n", + link.local_port, + to_of_node(link.local_node)->full_name); + v4l2_fwnode_put_link(&link); ret = -EINVAL; break; } @@ -114,25 +115,28 @@ static int xvip_graph_build_one(struct xvip_composite_device *xdev, if (local_pad->flags & MEDIA_PAD_FL_SINK) { dev_dbg(xdev->dev, "skipping sink port %s:%u\n", - link.local_node->full_name, link.local_port); - v4l2_of_put_link(&link); + to_of_node(link.local_node)->full_name, + link.local_port); + v4l2_fwnode_put_link(&link); continue; } /* Skip DMA engines, they will be processed separately. */ - if (link.remote_node == xdev->dev->of_node) { + if (link.remote_node == of_fwnode_handle(xdev->dev->of_node)) { dev_dbg(xdev->dev, "skipping DMA port %s:%u\n", - link.local_node->full_name, link.local_port); - v4l2_of_put_link(&link); + to_of_node(link.local_node)->full_name, + link.local_port); + v4l2_fwnode_put_link(&link); continue; } /* Find the remote entity. */ - ent = xvip_graph_find_entity(xdev, link.remote_node); + ent = xvip_graph_find_entity(xdev, + to_of_node(link.remote_node)); if (ent == NULL) { dev_err(xdev->dev, "no entity found for %s\n", - link.remote_node->full_name); - v4l2_of_put_link(&link); + to_of_node(link.remote_node)->full_name); + v4l2_fwnode_put_link(&link); ret = -ENODEV; break; } @@ -141,15 +145,16 @@ static int xvip_graph_build_one(struct xvip_composite_device *xdev, if (link.remote_port >= remote->num_pads) { dev_err(xdev->dev, "invalid port number %u on %s\n", - link.remote_port, link.remote_node->full_name); - v4l2_of_put_link(&link); + link.remote_port, + to_of_node(link.remote_node)->full_name); + v4l2_fwnode_put_link(&link); ret = -EINVAL; break; } remote_pad = &remote->pads[link.remote_port]; - v4l2_of_put_link(&link); + v4l2_fwnode_put_link(&link); /* Create the media link. */ dev_dbg(xdev->dev, "creating %s:%u -> %s:%u link\n", @@ -194,7 +199,7 @@ static int xvip_graph_build_dma(struct xvip_composite_device *xdev) struct media_pad *source_pad; struct media_pad *sink_pad; struct xvip_graph_entity *ent; - struct v4l2_of_link link; + struct v4l2_fwnode_link link; struct device_node *ep = NULL; struct device_node *next; struct xvip_dma *dma; @@ -213,7 +218,7 @@ static int xvip_graph_build_dma(struct xvip_composite_device *xdev) dev_dbg(xdev->dev, "processing endpoint %s\n", ep->full_name); - ret = v4l2_of_parse_link(ep, &link); + ret = v4l2_fwnode_parse_link(of_fwnode_handle(ep), &link); if (ret < 0) { dev_err(xdev->dev, "failed to parse link for %s\n", ep->full_name); @@ -225,7 +230,7 @@ static int xvip_graph_build_dma(struct xvip_composite_device *xdev) if (dma == NULL) { dev_err(xdev->dev, "no DMA engine found for port %u\n", link.local_port); - v4l2_of_put_link(&link); + v4l2_fwnode_put_link(&link); ret = -EINVAL; break; } @@ -234,19 +239,21 @@ static int xvip_graph_build_dma(struct xvip_composite_device *xdev) dma->video.name); /* Find the remote entity. */ - ent = xvip_graph_find_entity(xdev, link.remote_node); + ent = xvip_graph_find_entity(xdev, + to_of_node(link.remote_node)); if (ent == NULL) { dev_err(xdev->dev, "no entity found for %s\n", - link.remote_node->full_name); - v4l2_of_put_link(&link); + to_of_node(link.remote_node)->full_name); + v4l2_fwnode_put_link(&link); ret = -ENODEV; break; } if (link.remote_port >= ent->entity->num_pads) { dev_err(xdev->dev, "invalid port number %u on %s\n", - link.remote_port, link.remote_node->full_name); - v4l2_of_put_link(&link); + link.remote_port, + to_of_node(link.remote_node)->full_name); + v4l2_fwnode_put_link(&link); ret = -EINVAL; break; } @@ -263,7 +270,7 @@ static int xvip_graph_build_dma(struct xvip_composite_device *xdev) sink_pad = &dma->pad; } - v4l2_of_put_link(&link); + v4l2_fwnode_put_link(&link); /* Create the media link. */ dev_dbg(xdev->dev, "creating %s:%u -> %s:%u link\n", @@ -383,8 +390,8 @@ static int xvip_graph_parse_one(struct xvip_composite_device *xdev, } entity->node = remote; - entity->asd.match_type = V4L2_ASYNC_MATCH_OF; - entity->asd.match.of.node = remote; + entity->asd.match_type = V4L2_ASYNC_MATCH_FWNODE; + entity->asd.match.fwnode.fwnode = of_fwnode_handle(remote); list_add_tail(&entity->list, &xdev->entities); xdev->num_subdevs++; } diff --git a/drivers/media/rc/Kconfig b/drivers/media/rc/Kconfig index e422f3d56f76..5e83b76495f7 100644 --- a/drivers/media/rc/Kconfig +++ b/drivers/media/rc/Kconfig @@ -169,11 +169,11 @@ config IR_HIX5HD2 tristate "Hisilicon hix5hd2 IR remote control" depends on RC_CORE help - Say Y here if you want to use hisilicon hix5hd2 remote control. - To compile this driver as a module, choose M here: the module will be - called ir-hix5hd2. + Say Y here if you want to use hisilicon hix5hd2 remote control. + To compile this driver as a module, choose M here: the module will be + called ir-hix5hd2. - If you're not sure, select N here + If you're not sure, select N here config IR_IMON tristate "SoundGraph iMON Receiver and Display" diff --git a/drivers/media/rc/ati_remote.c b/drivers/media/rc/ati_remote.c index 9cf3e69de16a..a4c6ad4f67c1 100644 --- a/drivers/media/rc/ati_remote.c +++ b/drivers/media/rc/ati_remote.c @@ -904,9 +904,6 @@ static int ati_remote_probe(struct usb_interface *interface, if (err) goto exit_kill_urbs; - /* use our delay for rc_dev */ - ati_remote->rdev->input_dev->rep[REP_DELAY] = repeat_delay; - /* Set up and register mouse input device */ if (mouse) { input_dev = input_allocate_device(); diff --git a/drivers/media/rc/iguanair.c b/drivers/media/rc/iguanair.c index ccf24fd7ec1b..8711a7ff55cc 100644 --- a/drivers/media/rc/iguanair.c +++ b/drivers/media/rc/iguanair.c @@ -113,6 +113,7 @@ static void process_ir_data(struct iguanair *ir, unsigned len) break; case CMD_TX_OVERFLOW: ir->tx_overflow = true; + /* fall through */ case CMD_RECEIVER_OFF: case CMD_RECEIVER_ON: case CMD_SEND: diff --git a/drivers/media/rc/img-ir/img-ir-hw.c b/drivers/media/rc/img-ir/img-ir-hw.c index 431d33b36fb0..8d1439622533 100644 --- a/drivers/media/rc/img-ir/img-ir-hw.c +++ b/drivers/media/rc/img-ir/img-ir-hw.c @@ -702,10 +702,6 @@ static void img_ir_set_protocol(struct img_ir_priv *priv, u64 proto) { struct rc_dev *rdev = priv->hw.rdev; - spin_lock_irq(&rdev->rc_map.lock); - rdev->rc_map.rc_type = __ffs64(proto); - spin_unlock_irq(&rdev->rc_map.lock); - mutex_lock(&rdev->lock); rdev->enabled_protocols = proto; rdev->allowed_wakeup_protocols = proto; diff --git a/drivers/media/rc/imon.c b/drivers/media/rc/imon.c index 3489010601b5..bd76534a2749 100644 --- a/drivers/media/rc/imon.c +++ b/drivers/media/rc/imon.c @@ -1722,7 +1722,7 @@ static void imon_incoming_scancode(struct imon_context *ictx, if (kc == KEY_KEYBOARD && !ictx->release_code) { ictx->last_keycode = kc; if (!nomouse) { - ictx->pad_mouse = ~(ictx->pad_mouse) & 0x1; + ictx->pad_mouse = !ictx->pad_mouse; dev_dbg(dev, "toggling to %s mode\n", ictx->pad_mouse ? "mouse" : "keyboard"); spin_unlock_irqrestore(&ictx->kc_lock, flags); diff --git a/drivers/media/rc/ir-lirc-codec.c b/drivers/media/rc/ir-lirc-codec.c index de85f1d7ce43..a30af91710fe 100644 --- a/drivers/media/rc/ir-lirc-codec.c +++ b/drivers/media/rc/ir-lirc-codec.c @@ -327,16 +327,6 @@ static long ir_lirc_ioctl(struct file *filep, unsigned int cmd, return ret; } -static int ir_lirc_open(void *data) -{ - return 0; -} - -static void ir_lirc_close(void *data) -{ - return; -} - static const struct file_operations lirc_fops = { .owner = THIS_MODULE, .write = ir_lirc_transmit_ir, @@ -354,7 +344,6 @@ static const struct file_operations lirc_fops = { static int ir_lirc_register(struct rc_dev *dev) { struct lirc_driver *drv; - struct lirc_buffer *rbuf; int rc = -ENOMEM; unsigned long features = 0; @@ -362,19 +351,12 @@ static int ir_lirc_register(struct rc_dev *dev) if (!drv) return rc; - rbuf = kzalloc(sizeof(struct lirc_buffer), GFP_KERNEL); - if (!rbuf) - goto rbuf_alloc_failed; - - rc = lirc_buffer_init(rbuf, sizeof(int), LIRCBUF_SIZE); - if (rc) - goto rbuf_init_failed; - if (dev->driver_type != RC_DRIVER_IR_RAW_TX) { features |= LIRC_CAN_REC_MODE2; if (dev->rx_resolution) features |= LIRC_CAN_GET_REC_RESOLUTION; } + if (dev->tx_ir) { features |= LIRC_CAN_SEND_PULSE; if (dev->s_tx_mask) @@ -403,10 +385,10 @@ static int ir_lirc_register(struct rc_dev *dev) drv->minor = -1; drv->features = features; drv->data = &dev->raw->lirc; - drv->rbuf = rbuf; - drv->set_use_inc = &ir_lirc_open; - drv->set_use_dec = &ir_lirc_close; + drv->rbuf = NULL; drv->code_length = sizeof(struct ir_raw_event) * 8; + drv->chunk_size = sizeof(int); + drv->buffer_size = LIRCBUF_SIZE; drv->fops = &lirc_fops; drv->dev = &dev->dev; drv->rdev = dev; @@ -415,19 +397,15 @@ static int ir_lirc_register(struct rc_dev *dev) drv->minor = lirc_register_driver(drv); if (drv->minor < 0) { rc = -ENODEV; - goto lirc_register_failed; + goto out; } dev->raw->lirc.drv = drv; dev->raw->lirc.dev = dev; return 0; -lirc_register_failed: -rbuf_init_failed: - kfree(rbuf); -rbuf_alloc_failed: +out: kfree(drv); - return rc; } @@ -436,9 +414,8 @@ static int ir_lirc_unregister(struct rc_dev *dev) struct lirc_codec *lirc = &dev->raw->lirc; lirc_unregister_driver(lirc->drv->minor); - lirc_buffer_free(lirc->drv->rbuf); - kfree(lirc->drv->rbuf); kfree(lirc->drv); + lirc->drv = NULL; return 0; } diff --git a/drivers/media/rc/ir-spi.c b/drivers/media/rc/ir-spi.c index c8863f36686a..7e383b3fedd5 100644 --- a/drivers/media/rc/ir-spi.c +++ b/drivers/media/rc/ir-spi.c @@ -57,10 +57,13 @@ static int ir_spi_tx(struct rc_dev *dev, /* convert the pulse/space signal to raw binary signal */ for (i = 0; i < count; i++) { + unsigned int periods; int j; - u16 val = ((i + 1) % 2) ? idata->pulse : idata->space; + u16 val; - if (len + buffer[i] >= IR_SPI_MAX_BUFSIZE) + periods = DIV_ROUND_CLOSEST(buffer[i] * idata->freq, 1000000); + + if (len + periods >= IR_SPI_MAX_BUFSIZE) return -EINVAL; /* @@ -69,13 +72,13 @@ static int ir_spi_tx(struct rc_dev *dev, * contain a space duration. */ val = (i % 2) ? idata->space : idata->pulse; - for (j = 0; j < buffer[i]; j++) + for (j = 0; j < periods; j++) idata->tx_buf[len++] = val; } memset(&xfer, 0, sizeof(xfer)); - xfer.speed_hz = idata->freq; + xfer.speed_hz = idata->freq * 16; xfer.len = len * sizeof(*idata->tx_buf); xfer.tx_buf = idata->tx_buf; diff --git a/drivers/media/rc/lirc_dev.c b/drivers/media/rc/lirc_dev.c index 8d60c9f00df9..db1e7b70c998 100644 --- a/drivers/media/rc/lirc_dev.c +++ b/drivers/media/rc/lirc_dev.c @@ -18,18 +18,10 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/module.h> -#include <linux/kernel.h> #include <linux/sched/signal.h> -#include <linux/errno.h> #include <linux/ioctl.h> -#include <linux/fs.h> #include <linux/poll.h> -#include <linux/completion.h> #include <linux/mutex.h> -#include <linux/wait.h> -#include <linux/unistd.h> -#include <linux/kthread.h> -#include <linux/bitops.h> #include <linux/device.h> #include <linux/cdev.h> @@ -37,9 +29,6 @@ #include <media/lirc.h> #include <media/lirc_dev.h> -static bool debug; - -#define IRCTL_DEV_NAME "BaseRemoteCtl" #define NOPLUG -1 #define LOGHEAD "lirc_dev (%s[%d]): " @@ -52,13 +41,11 @@ struct irctl { struct mutex irctl_lock; struct lirc_buffer *buf; + bool buf_internal; unsigned int chunk_size; struct device dev; struct cdev cdev; - - struct task_struct *task; - long jiffies_to_wait; }; static DEFINE_MUTEX(lirc_dev_lock); @@ -68,22 +55,11 @@ static struct irctl *irctls[MAX_IRCTL_DEVICES]; /* Only used for sysfs but defined to void otherwise */ static struct class *lirc_class; -/* helper function - * initializes the irctl structure - */ -static void lirc_irctl_init(struct irctl *ir) -{ - mutex_init(&ir->irctl_lock); - ir->d.minor = NOPLUG; -} - static void lirc_release(struct device *ld) { struct irctl *ir = container_of(ld, struct irctl, dev); - put_device(ir->dev.parent); - - if (ir->buf != ir->d.rbuf) { + if (ir->buf_internal) { lirc_buffer_free(ir->buf); kfree(ir->buf); } @@ -94,93 +70,6 @@ static void lirc_release(struct device *ld) kfree(ir); } -/* helper function - * reads key codes from driver and puts them into buffer - * returns 0 on success - */ -static int lirc_add_to_buf(struct irctl *ir) -{ - int res; - int got_data = -1; - - if (!ir->d.add_to_buf) - return 0; - - /* - * service the device as long as it is returning - * data and we have space - */ - do { - got_data++; - res = ir->d.add_to_buf(ir->d.data, ir->buf); - } while (!res); - - if (res == -ENODEV) - kthread_stop(ir->task); - - return got_data ? 0 : res; -} - -/* main function of the polling thread - */ -static int lirc_thread(void *irctl) -{ - struct irctl *ir = irctl; - - do { - if (ir->open) { - if (ir->jiffies_to_wait) { - set_current_state(TASK_INTERRUPTIBLE); - schedule_timeout(ir->jiffies_to_wait); - } - if (kthread_should_stop()) - break; - if (!lirc_add_to_buf(ir)) - wake_up_interruptible(&ir->buf->wait_poll); - } else { - set_current_state(TASK_INTERRUPTIBLE); - schedule(); - } - } while (!kthread_should_stop()); - - return 0; -} - - -static const struct file_operations lirc_dev_fops = { - .owner = THIS_MODULE, - .read = lirc_dev_fop_read, - .write = lirc_dev_fop_write, - .poll = lirc_dev_fop_poll, - .unlocked_ioctl = lirc_dev_fop_ioctl, - .open = lirc_dev_fop_open, - .release = lirc_dev_fop_close, - .llseek = noop_llseek, -}; - -static int lirc_cdev_add(struct irctl *ir) -{ - struct lirc_driver *d = &ir->d; - struct cdev *cdev; - int retval; - - cdev = &ir->cdev; - - if (d->fops) { - cdev_init(cdev, d->fops); - cdev->owner = d->owner; - } else { - cdev_init(cdev, &lirc_dev_fops); - cdev->owner = THIS_MODULE; - } - retval = kobject_set_name(&cdev->kobj, "lirc%d", d->minor); - if (retval) - return retval; - - cdev->kobj.parent = &ir->dev.kobj; - return cdev_add(cdev, ir->dev.devt, 1); -} - static int lirc_allocate_buffer(struct irctl *ir) { int err = 0; @@ -189,8 +78,6 @@ static int lirc_allocate_buffer(struct irctl *ir) unsigned int buffer_size; struct lirc_driver *d = &ir->d; - mutex_lock(&lirc_dev_lock); - bytes_in_key = BITS_TO_LONGS(d->code_length) + (d->code_length % 8 ? 1 : 0); buffer_size = d->buffer_size ? d->buffer_size : BUFLEN / bytes_in_key; @@ -198,6 +85,7 @@ static int lirc_allocate_buffer(struct irctl *ir) if (d->rbuf) { ir->buf = d->rbuf; + ir->buf_internal = false; } else { ir->buf = kmalloc(sizeof(struct lirc_buffer), GFP_KERNEL); if (!ir->buf) { @@ -208,18 +96,20 @@ static int lirc_allocate_buffer(struct irctl *ir) err = lirc_buffer_init(ir->buf, chunk_size, buffer_size); if (err) { kfree(ir->buf); + ir->buf = NULL; goto out; } + + ir->buf_internal = true; + d->rbuf = ir->buf; } ir->chunk_size = ir->buf->chunk_size; out: - mutex_unlock(&lirc_dev_lock); - return err; } -static int lirc_allocate_driver(struct lirc_driver *d) +int lirc_register_driver(struct lirc_driver *d) { struct irctl *ir; int minor; @@ -235,6 +125,11 @@ static int lirc_allocate_driver(struct lirc_driver *d) return -EINVAL; } + if (!d->fops) { + pr_err("fops pointer not filled in!\n"); + return -EINVAL; + } + if (d->minor >= MAX_IRCTL_DEVICES) { dev_err(d->dev, "minor must be between 0 and %d!\n", MAX_IRCTL_DEVICES - 1); @@ -247,18 +142,8 @@ static int lirc_allocate_driver(struct lirc_driver *d) return -EBADRQC; } - if (d->sample_rate) { - if (2 > d->sample_rate || HZ < d->sample_rate) { - dev_err(d->dev, "invalid %d sample rate\n", - d->sample_rate); - return -EBADRQC; - } - if (!d->add_to_buf) { - dev_err(d->dev, "add_to_buf not set\n"); - return -EBADRQC; - } - } else if (!d->rbuf && !(d->fops && d->fops->read && - d->fops->poll && d->fops->unlocked_ioctl)) { + if (!d->rbuf && !(d->fops && d->fops->read && + d->fops->poll && d->fops->unlocked_ioctl)) { dev_err(d->dev, "undefined read, poll, ioctl\n"); return -EBADRQC; } @@ -288,7 +173,8 @@ static int lirc_allocate_driver(struct lirc_driver *d) err = -ENOMEM; goto out_lock; } - lirc_irctl_init(ir); + + mutex_init(&ir->irctl_lock); irctls[minor] = ir; d->minor = minor; @@ -300,32 +186,29 @@ static int lirc_allocate_driver(struct lirc_driver *d) ir->d = *d; + if (LIRC_CAN_REC(d->features)) { + err = lirc_allocate_buffer(irctls[minor]); + if (err) { + kfree(ir); + goto out_lock; + } + d->rbuf = ir->buf; + } + + device_initialize(&ir->dev); ir->dev.devt = MKDEV(MAJOR(lirc_base_dev), ir->d.minor); ir->dev.class = lirc_class; ir->dev.parent = d->dev; ir->dev.release = lirc_release; dev_set_name(&ir->dev, "lirc%d", ir->d.minor); - device_initialize(&ir->dev); - if (d->sample_rate) { - ir->jiffies_to_wait = HZ / d->sample_rate; + cdev_init(&ir->cdev, d->fops); + ir->cdev.owner = ir->d.owner; + ir->cdev.kobj.parent = &ir->dev.kobj; - /* try to fire up polling thread */ - ir->task = kthread_run(lirc_thread, (void *)ir, "lirc_dev"); - if (IS_ERR(ir->task)) { - dev_err(d->dev, "cannot run thread for minor = %d\n", - d->minor); - err = -ECHILD; - goto out_sysfs; - } - } else { - /* it means - wait for external event in task queue */ - ir->jiffies_to_wait = 0; - } - - err = lirc_cdev_add(ir); + err = cdev_add(&ir->cdev, ir->dev.devt, 1); if (err) - goto out_sysfs; + goto out_free_dev; ir->attached = 1; @@ -335,37 +218,20 @@ static int lirc_allocate_driver(struct lirc_driver *d) mutex_unlock(&lirc_dev_lock); - get_device(ir->dev.parent); - dev_info(ir->d.dev, "lirc_dev: driver %s registered at minor = %d\n", ir->d.name, ir->d.minor); + return minor; + out_cdev: cdev_del(&ir->cdev); -out_sysfs: +out_free_dev: put_device(&ir->dev); out_lock: mutex_unlock(&lirc_dev_lock); return err; } - -int lirc_register_driver(struct lirc_driver *d) -{ - int minor, err = 0; - - minor = lirc_allocate_driver(d); - if (minor < 0) - return minor; - - if (LIRC_CAN_REC(d->features)) { - err = lirc_allocate_buffer(irctls[minor]); - if (err) - lirc_unregister_driver(minor); - } - - return err ? err : minor; -} EXPORT_SYMBOL(lirc_register_driver); int lirc_unregister_driver(int minor) @@ -393,10 +259,6 @@ int lirc_unregister_driver(int minor) return -ENOENT; } - /* end up polling thread */ - if (ir->task) - kthread_stop(ir->task); - dev_dbg(ir->d.dev, "lirc_dev: driver %s unregistered from minor = %d\n", ir->d.name, ir->d.minor); @@ -407,12 +269,6 @@ int lirc_unregister_driver(int minor) wake_up_interruptible(&ir->buf->wait_poll); } - mutex_lock(&ir->irctl_lock); - - if (ir->d.set_use_dec) - ir->d.set_use_dec(ir->d.data); - - mutex_unlock(&ir->irctl_lock); mutex_unlock(&lirc_dev_lock); device_del(&ir->dev); @@ -462,17 +318,10 @@ int lirc_dev_fop_open(struct inode *inode, struct file *file) goto error; } + if (ir->buf) + lirc_buffer_clear(ir->buf); + ir->open++; - if (ir->d.set_use_inc) - retval = ir->d.set_use_inc(ir->d.data); - if (retval) { - ir->open--; - } else { - if (ir->buf) - lirc_buffer_clear(ir->buf); - if (ir->task) - wake_up_process(ir->task); - } error: nonseekable_open(inode, file); @@ -497,8 +346,6 @@ int lirc_dev_fop_close(struct inode *inode, struct file *file) rc_close(ir->d.rdev); ir->open--; - if (ir->d.set_use_dec) - ir->d.set_use_dec(ir->d.data); if (!ret) mutex_unlock(&lirc_dev_lock); @@ -517,7 +364,7 @@ unsigned int lirc_dev_fop_poll(struct file *file, poll_table *wait) } if (!ir->attached) - return POLLERR; + return POLLHUP | POLLERR; if (ir->buf) { poll_wait(file, &ir->buf->wait_poll, wait); @@ -729,24 +576,6 @@ void *lirc_get_pdata(struct file *file) EXPORT_SYMBOL(lirc_get_pdata); -ssize_t lirc_dev_fop_write(struct file *file, const char __user *buffer, - size_t length, loff_t *ppos) -{ - struct irctl *ir = irctls[iminor(file_inode(file))]; - - if (!ir) { - pr_err("called with invalid irctl\n"); - return -ENODEV; - } - - if (!ir->attached) - return -ENODEV; - - return -EINVAL; -} -EXPORT_SYMBOL(lirc_dev_fop_write); - - static int __init lirc_dev_init(void) { int retval; @@ -758,7 +587,7 @@ static int __init lirc_dev_init(void) } retval = alloc_chrdev_region(&lirc_base_dev, 0, MAX_IRCTL_DEVICES, - IRCTL_DEV_NAME); + "BaseRemoteCtl"); if (retval) { class_destroy(lirc_class); pr_err("alloc_chrdev_region failed\n"); @@ -784,6 +613,3 @@ module_exit(lirc_dev_exit); MODULE_DESCRIPTION("LIRC base driver module"); MODULE_AUTHOR("Artur Lipowski"); MODULE_LICENSE("GPL"); - -module_param(debug, bool, S_IRUGO | S_IWUSR); -MODULE_PARM_DESC(debug, "Enable debugging messages"); diff --git a/drivers/media/rc/mceusb.c b/drivers/media/rc/mceusb.c index 93b16fe3ab38..eb130694bbb8 100644 --- a/drivers/media/rc/mceusb.c +++ b/drivers/media/rc/mceusb.c @@ -36,18 +36,18 @@ #include <linux/device.h> #include <linux/module.h> #include <linux/slab.h> +#include <linux/workqueue.h> #include <linux/usb.h> #include <linux/usb/input.h> #include <linux/pm_wakeup.h> #include <media/rc-core.h> -#define DRIVER_VERSION "1.92" +#define DRIVER_VERSION "1.93" #define DRIVER_AUTHOR "Jarod Wilson <jarod@redhat.com>" #define DRIVER_DESC "Windows Media Center Ed. eHome Infrared Transceiver " \ "device driver" #define DRIVER_NAME "mceusb" -#define USB_BUFLEN 32 /* USB reception buffer length */ #define USB_CTRL_MSG_SZ 2 /* Size of usb ctrl msg on gen1 hw */ #define MCE_G1_INIT_MSGS 40 /* Init messages on gen1 hw to throw out */ @@ -417,7 +417,9 @@ struct mceusb_dev { /* usb */ struct usb_device *usbdev; struct urb *urb_in; + unsigned int pipe_in; struct usb_endpoint_descriptor *usb_ep_out; + unsigned int pipe_out; /* buffers and dma */ unsigned char *buf_in; @@ -454,6 +456,16 @@ struct mceusb_dev { u8 num_rxports; /* number of receive sensors */ u8 txports_cabled; /* bitmask of transmitters with cable */ u8 rxports_active; /* bitmask of active receive sensors */ + + /* + * support for async error handler mceusb_deferred_kevent() + * where usb_clear_halt(), usb_reset_configuration(), + * usb_reset_device(), etc. must be done in process context + */ + struct work_struct kevent; + unsigned long kevent_flags; +# define EVENT_TX_HALT 0 +# define EVENT_RX_HALT 1 }; /* MCE Device Command Strings, generally a port and command pair */ @@ -527,7 +539,7 @@ static int mceusb_cmd_datasize(u8 cmd, u8 subcmd) } static void mceusb_dev_printdata(struct mceusb_dev *ir, char *buf, - int offset, int len, bool out) + int buf_len, int offset, int len, bool out) { #if defined(DEBUG) || defined(CONFIG_DYNAMIC_DEBUG) char *inout; @@ -544,7 +556,8 @@ static void mceusb_dev_printdata(struct mceusb_dev *ir, char *buf, return; dev_dbg(dev, "%cx data: %*ph (length=%d)", - (out ? 't' : 'r'), min(len, USB_BUFLEN), buf, len); + (out ? 't' : 'r'), + min(len, buf_len - offset), buf + offset, len); inout = out ? "Request" : "Got"; @@ -686,6 +699,21 @@ static void mceusb_dev_printdata(struct mceusb_dev *ir, char *buf, #endif } +/* + * Schedule work that can't be done in interrupt handlers + * (mceusb_dev_recv() and mce_async_callback()) nor tasklets. + * Invokes mceusb_deferred_kevent() for recovering from + * error events specified by the kevent bit field. + */ +static void mceusb_defer_kevent(struct mceusb_dev *ir, int kevent) +{ + set_bit(kevent, &ir->kevent_flags); + if (!schedule_work(&ir->kevent)) + dev_err(ir->dev, "kevent %d may have been dropped", kevent); + else + dev_dbg(ir->dev, "kevent %d scheduled", kevent); +} + static void mce_async_callback(struct urb *urb) { struct mceusb_dev *ir; @@ -701,7 +729,8 @@ static void mce_async_callback(struct urb *urb) case 0: len = urb->actual_length; - mceusb_dev_printdata(ir, urb->transfer_buffer, 0, len, true); + mceusb_dev_printdata(ir, urb->transfer_buffer, len, + 0, len, true); break; case -ECONNRESET: @@ -711,6 +740,11 @@ static void mce_async_callback(struct urb *urb) break; case -EPIPE: + dev_err(ir->dev, "Error: request urb status = %d (TX HALT)", + urb->status); + mceusb_defer_kevent(ir, EVENT_TX_HALT); + break; + default: dev_err(ir->dev, "Error: request urb status = %d", urb->status); break; @@ -721,18 +755,18 @@ static void mce_async_callback(struct urb *urb) usb_free_urb(urb); } -/* request incoming or send outgoing usb packet - used to initialize remote */ +/* request outgoing (send) usb packet - used to initialize remote */ static void mce_request_packet(struct mceusb_dev *ir, unsigned char *data, int size) { - int res, pipe; + int res; struct urb *async_urb; struct device *dev = ir->dev; unsigned char *async_buf; async_urb = usb_alloc_urb(0, GFP_KERNEL); if (unlikely(!async_urb)) { - dev_err(dev, "Error, couldn't allocate urb!\n"); + dev_err(dev, "Error, couldn't allocate urb!"); return; } @@ -743,32 +777,26 @@ static void mce_request_packet(struct mceusb_dev *ir, unsigned char *data, } /* outbound data */ - if (usb_endpoint_xfer_int(ir->usb_ep_out)) { - pipe = usb_sndintpipe(ir->usbdev, - ir->usb_ep_out->bEndpointAddress); - usb_fill_int_urb(async_urb, ir->usbdev, pipe, async_buf, - size, mce_async_callback, ir, + if (usb_endpoint_xfer_int(ir->usb_ep_out)) + usb_fill_int_urb(async_urb, ir->usbdev, ir->pipe_out, + async_buf, size, mce_async_callback, ir, ir->usb_ep_out->bInterval); - } else { - pipe = usb_sndbulkpipe(ir->usbdev, - ir->usb_ep_out->bEndpointAddress); - usb_fill_bulk_urb(async_urb, ir->usbdev, pipe, - async_buf, size, mce_async_callback, - ir); - } - memcpy(async_buf, data, size); + else + usb_fill_bulk_urb(async_urb, ir->usbdev, ir->pipe_out, + async_buf, size, mce_async_callback, ir); - dev_dbg(dev, "receive request called (size=%#x)", size); + memcpy(async_buf, data, size); - async_urb->transfer_buffer_length = size; - async_urb->dev = ir->usbdev; + dev_dbg(dev, "send request called (size=%#x)", size); res = usb_submit_urb(async_urb, GFP_ATOMIC); if (res) { - dev_err(dev, "receive request FAILED! (res=%d)", res); + dev_err(dev, "send request FAILED! (res=%d)", res); + kfree(async_buf); + usb_free_urb(async_urb); return; } - dev_dbg(dev, "receive request complete (res=%d)", res); + dev_dbg(dev, "send request complete (res=%d)", res); } static void mce_async_out(struct mceusb_dev *ir, unsigned char *data, int size) @@ -974,7 +1002,7 @@ static void mceusb_process_ir_data(struct mceusb_dev *ir, int buf_len) switch (ir->parser_state) { case SUBCMD: ir->rem = mceusb_cmd_datasize(ir->cmd, ir->buf_in[i]); - mceusb_dev_printdata(ir, ir->buf_in, i - 1, + mceusb_dev_printdata(ir, ir->buf_in, buf_len, i - 1, ir->rem + 2, false); mceusb_handle_command(ir, i); ir->parser_state = CMD_DATA; @@ -986,7 +1014,7 @@ static void mceusb_process_ir_data(struct mceusb_dev *ir, int buf_len) rawir.duration = (ir->buf_in[i] & MCE_PULSE_MASK) * US_TO_NS(MCE_TIME_UNIT); - dev_dbg(ir->dev, "Storing %s with duration %d", + dev_dbg(ir->dev, "Storing %s with duration %u", rawir.pulse ? "pulse" : "space", rawir.duration); @@ -1007,7 +1035,7 @@ static void mceusb_process_ir_data(struct mceusb_dev *ir, int buf_len) continue; } ir->rem = (ir->cmd & MCE_PACKET_LENGTH_MASK); - mceusb_dev_printdata(ir, ir->buf_in, + mceusb_dev_printdata(ir, ir->buf_in, buf_len, i, ir->rem + 1, false); if (ir->rem) ir->parser_state = PARSE_IRDATA; @@ -1052,6 +1080,11 @@ static void mceusb_dev_recv(struct urb *urb) return; case -EPIPE: + dev_err(ir->dev, "Error: urb status = %d (RX HALT)", + urb->status); + mceusb_defer_kevent(ir, EVENT_RX_HALT); + return; + default: dev_err(ir->dev, "Error: urb status = %d", urb->status); break; @@ -1170,6 +1203,45 @@ static void mceusb_flash_led(struct mceusb_dev *ir) mce_async_out(ir, FLASH_LED, sizeof(FLASH_LED)); } +/* + * Workqueue function + * for resetting or recovering device after occurrence of error events + * specified in ir->kevent bit field. + * Function runs (via schedule_work()) in non-interrupt context, for + * calls here (such as usb_clear_halt()) requiring non-interrupt context. + */ +static void mceusb_deferred_kevent(struct work_struct *work) +{ + struct mceusb_dev *ir = + container_of(work, struct mceusb_dev, kevent); + int status; + + if (test_bit(EVENT_RX_HALT, &ir->kevent_flags)) { + usb_unlink_urb(ir->urb_in); + status = usb_clear_halt(ir->usbdev, ir->pipe_in); + if (status < 0) { + dev_err(ir->dev, "rx clear halt error %d", + status); + } + clear_bit(EVENT_RX_HALT, &ir->kevent_flags); + if (status == 0) { + status = usb_submit_urb(ir->urb_in, GFP_KERNEL); + if (status < 0) { + dev_err(ir->dev, + "rx unhalt submit urb error %d", + status); + } + } + } + + if (test_bit(EVENT_TX_HALT, &ir->kevent_flags)) { + status = usb_clear_halt(ir->usbdev, ir->pipe_out); + if (status < 0) + dev_err(ir->dev, "tx clear halt error %d", status); + clear_bit(EVENT_TX_HALT, &ir->kevent_flags); + } +} + static struct rc_dev *mceusb_init_rc_dev(struct mceusb_dev *ir) { struct usb_device *udev = ir->usbdev; @@ -1303,6 +1375,7 @@ static int mceusb_dev_probe(struct usb_interface *intf, if (!ir) goto mem_alloc_fail; + ir->pipe_in = pipe; ir->buf_in = usb_alloc_coherent(dev, maxp, GFP_ATOMIC, &ir->dma_in); if (!ir->buf_in) goto buf_in_alloc_fail; @@ -1321,6 +1394,12 @@ static int mceusb_dev_probe(struct usb_interface *intf, /* Saving usb interface data for use by the transmitter routine */ ir->usb_ep_out = ep_out; + if (usb_endpoint_xfer_int(ep_out)) + ir->pipe_out = usb_sndintpipe(ir->usbdev, + ep_out->bEndpointAddress); + else + ir->pipe_out = usb_sndbulkpipe(ir->usbdev, + ep_out->bEndpointAddress); if (dev->descriptor.iManufacturer && usb_string(dev, dev->descriptor.iManufacturer, @@ -1332,21 +1411,32 @@ static int mceusb_dev_probe(struct usb_interface *intf, snprintf(name + strlen(name), sizeof(name) - strlen(name), " %s", buf); + /* + * Initialize async USB error handler before registering + * or activating any mceusb RX and TX functions + */ + INIT_WORK(&ir->kevent, mceusb_deferred_kevent); + ir->rc = mceusb_init_rc_dev(ir); if (!ir->rc) goto rc_dev_fail; /* wire up inbound data handler */ - usb_fill_int_urb(ir->urb_in, dev, pipe, ir->buf_in, maxp, - mceusb_dev_recv, ir, ep_in->bInterval); + if (usb_endpoint_xfer_int(ep_in)) + usb_fill_int_urb(ir->urb_in, dev, pipe, ir->buf_in, maxp, + mceusb_dev_recv, ir, ep_in->bInterval); + else + usb_fill_bulk_urb(ir->urb_in, dev, pipe, ir->buf_in, maxp, + mceusb_dev_recv, ir); + ir->urb_in->transfer_dma = ir->dma_in; ir->urb_in->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; /* flush buffers on the device */ - dev_dbg(&intf->dev, "Flushing receive buffers\n"); + dev_dbg(&intf->dev, "Flushing receive buffers"); res = usb_submit_urb(ir->urb_in, GFP_KERNEL); if (res) - dev_err(&intf->dev, "failed to flush buffers: %d\n", res); + dev_err(&intf->dev, "failed to flush buffers: %d", res); /* figure out which firmware/emulator version this hardware has */ mceusb_get_emulator_version(ir); @@ -1380,6 +1470,7 @@ static int mceusb_dev_probe(struct usb_interface *intf, /* Error-handling path */ rc_dev_fail: + cancel_work_sync(&ir->kevent); usb_put_dev(ir->usbdev); usb_kill_urb(ir->urb_in); usb_free_urb(ir->urb_in); @@ -1405,6 +1496,7 @@ static void mceusb_dev_disconnect(struct usb_interface *intf) return; ir->usbdev = NULL; + cancel_work_sync(&ir->kevent); rc_unregister_device(ir->rc); usb_kill_urb(ir->urb_in); usb_free_urb(ir->urb_in); diff --git a/drivers/media/rc/meson-ir.c b/drivers/media/rc/meson-ir.c index 5576dbd6b1a4..65566d569cb1 100644 --- a/drivers/media/rc/meson-ir.c +++ b/drivers/media/rc/meson-ir.c @@ -19,6 +19,7 @@ #include <linux/of_platform.h> #include <linux/platform_device.h> #include <linux/spinlock.h> +#include <linux/bitfield.h> #include <media/rc-core.h> @@ -36,7 +37,7 @@ /* only available on Meson 8b and newer */ #define IR_DEC_REG2 0x20 -#define REG0_RATE_MASK (BIT(11) - 1) +#define REG0_RATE_MASK GENMASK(11, 0) #define DECODE_MODE_NEC 0x0 #define DECODE_MODE_RAW 0x2 @@ -49,14 +50,13 @@ #define REG2_MODE_MASK GENMASK(3, 0) #define REG2_MODE_SHIFT 0 -#define REG1_TIME_IV_SHIFT 16 -#define REG1_TIME_IV_MASK ((BIT(13) - 1) << REG1_TIME_IV_SHIFT) +#define REG1_TIME_IV_MASK GENMASK(28, 16) -#define REG1_IRQSEL_MASK (BIT(2) | BIT(3)) -#define REG1_IRQSEL_NEC_MODE (0 << 2) -#define REG1_IRQSEL_RISE_FALL (1 << 2) -#define REG1_IRQSEL_FALL (2 << 2) -#define REG1_IRQSEL_RISE (3 << 2) +#define REG1_IRQSEL_MASK GENMASK(3, 2) +#define REG1_IRQSEL_NEC_MODE 0 +#define REG1_IRQSEL_RISE_FALL 1 +#define REG1_IRQSEL_FALL 2 +#define REG1_IRQSEL_RISE 3 #define REG1_RESET BIT(0) #define REG1_ENABLE BIT(15) @@ -68,7 +68,6 @@ struct meson_ir { void __iomem *reg; struct rc_dev *rc; - int irq; spinlock_t lock; }; @@ -86,18 +85,19 @@ static void meson_ir_set_mask(struct meson_ir *ir, unsigned int reg, static irqreturn_t meson_ir_irq(int irqno, void *dev_id) { struct meson_ir *ir = dev_id; - u32 duration; + u32 duration, status; DEFINE_IR_RAW_EVENT(rawir); spin_lock(&ir->lock); - duration = readl(ir->reg + IR_DEC_REG1); - duration = (duration & REG1_TIME_IV_MASK) >> REG1_TIME_IV_SHIFT; + duration = readl_relaxed(ir->reg + IR_DEC_REG1); + duration = FIELD_GET(REG1_TIME_IV_MASK, duration); rawir.duration = US_TO_NS(duration * MESON_TRATE); - rawir.pulse = !!(readl(ir->reg + IR_DEC_STATUS) & STATUS_IR_DEC_IN); + status = readl_relaxed(ir->reg + IR_DEC_STATUS); + rawir.pulse = !!(status & STATUS_IR_DEC_IN); - ir_raw_event_store_with_filter(ir->rc, &rawir); + ir_raw_event_store(ir->rc, &rawir); ir_raw_event_handle(ir->rc); spin_unlock(&ir->lock); @@ -112,7 +112,7 @@ static int meson_ir_probe(struct platform_device *pdev) struct resource *res; const char *map_name; struct meson_ir *ir; - int ret; + int irq, ret; ir = devm_kzalloc(dev, sizeof(struct meson_ir), GFP_KERNEL); if (!ir) @@ -125,13 +125,13 @@ static int meson_ir_probe(struct platform_device *pdev) return PTR_ERR(ir->reg); } - ir->irq = platform_get_irq(pdev, 0); - if (ir->irq < 0) { + irq = platform_get_irq(pdev, 0); + if (irq < 0) { dev_err(dev, "no irq resource\n"); - return ir->irq; + return irq; } - ir->rc = rc_allocate_device(RC_DRIVER_IR_RAW); + ir->rc = devm_rc_allocate_device(dev, RC_DRIVER_IR_RAW); if (!ir->rc) { dev_err(dev, "failed to allocate rc device\n"); return -ENOMEM; @@ -143,7 +143,6 @@ static int meson_ir_probe(struct platform_device *pdev) ir->rc->input_id.bustype = BUS_HOST; map_name = of_get_property(node, "linux,rc-map-name", NULL); ir->rc->map_name = map_name ? map_name : RC_MAP_EMPTY; - ir->rc->dev.parent = dev; ir->rc->allowed_protocols = RC_BIT_ALL_IR_DECODER; ir->rc->rx_resolution = US_TO_NS(MESON_TRATE); ir->rc->timeout = MS_TO_NS(200); @@ -152,16 +151,16 @@ static int meson_ir_probe(struct platform_device *pdev) spin_lock_init(&ir->lock); platform_set_drvdata(pdev, ir); - ret = rc_register_device(ir->rc); + ret = devm_rc_register_device(dev, ir->rc); if (ret) { dev_err(dev, "failed to register rc device\n"); - goto out_free; + return ret; } - ret = devm_request_irq(dev, ir->irq, meson_ir_irq, 0, "ir-meson", ir); + ret = devm_request_irq(dev, irq, meson_ir_irq, 0, NULL, ir); if (ret) { dev_err(dev, "failed to request irq\n"); - goto out_unreg; + return ret; } /* Reset the decoder */ @@ -171,29 +170,22 @@ static int meson_ir_probe(struct platform_device *pdev) /* Set general operation mode (= raw/software decoding) */ if (of_device_is_compatible(node, "amlogic,meson6-ir")) meson_ir_set_mask(ir, IR_DEC_REG1, REG1_MODE_MASK, - DECODE_MODE_RAW << REG1_MODE_SHIFT); + FIELD_PREP(REG1_MODE_MASK, DECODE_MODE_RAW)); else meson_ir_set_mask(ir, IR_DEC_REG2, REG2_MODE_MASK, - DECODE_MODE_RAW << REG2_MODE_SHIFT); + FIELD_PREP(REG2_MODE_MASK, DECODE_MODE_RAW)); /* Set rate */ meson_ir_set_mask(ir, IR_DEC_REG0, REG0_RATE_MASK, MESON_TRATE - 1); /* IRQ on rising and falling edges */ meson_ir_set_mask(ir, IR_DEC_REG1, REG1_IRQSEL_MASK, - REG1_IRQSEL_RISE_FALL); + FIELD_PREP(REG1_IRQSEL_MASK, REG1_IRQSEL_RISE_FALL)); /* Enable the decoder */ meson_ir_set_mask(ir, IR_DEC_REG1, REG1_ENABLE, REG1_ENABLE); dev_info(dev, "receiver initialized\n"); return 0; -out_unreg: - rc_unregister_device(ir->rc); - ir->rc = NULL; -out_free: - rc_free_device(ir->rc); - - return ret; } static int meson_ir_remove(struct platform_device *pdev) @@ -206,11 +198,35 @@ static int meson_ir_remove(struct platform_device *pdev) meson_ir_set_mask(ir, IR_DEC_REG1, REG1_ENABLE, 0); spin_unlock_irqrestore(&ir->lock, flags); - rc_unregister_device(ir->rc); - return 0; } +static void meson_ir_shutdown(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct meson_ir *ir = platform_get_drvdata(pdev); + unsigned long flags; + + spin_lock_irqsave(&ir->lock, flags); + + /* + * Set operation mode to NEC/hardware decoding to give + * bootloader a chance to power the system back on + */ + if (of_device_is_compatible(node, "amlogic,meson6-ir")) + meson_ir_set_mask(ir, IR_DEC_REG1, REG1_MODE_MASK, + DECODE_MODE_NEC << REG1_MODE_SHIFT); + else + meson_ir_set_mask(ir, IR_DEC_REG2, REG2_MODE_MASK, + DECODE_MODE_NEC << REG2_MODE_SHIFT); + + /* Set rate to default value */ + meson_ir_set_mask(ir, IR_DEC_REG0, REG0_RATE_MASK, 0x13); + + spin_unlock_irqrestore(&ir->lock, flags); +} + static const struct of_device_id meson_ir_match[] = { { .compatible = "amlogic,meson6-ir" }, { .compatible = "amlogic,meson8b-ir" }, @@ -222,6 +238,7 @@ MODULE_DEVICE_TABLE(of, meson_ir_match); static struct platform_driver meson_ir_driver = { .probe = meson_ir_probe, .remove = meson_ir_remove, + .shutdown = meson_ir_shutdown, .driver = { .name = DRIVER_NAME, .of_match_table = meson_ir_match, diff --git a/drivers/media/rc/rc-core-priv.h b/drivers/media/rc/rc-core-priv.h index 0455b273c2fc..b3e7cac2c3ee 100644 --- a/drivers/media/rc/rc-core-priv.h +++ b/drivers/media/rc/rc-core-priv.h @@ -263,7 +263,9 @@ int ir_raw_gen_pl(struct ir_raw_event **ev, unsigned int max, * Routines from rc-raw.c to be used internally and by decoders */ u64 ir_raw_get_allowed_protocols(void); +int ir_raw_event_prepare(struct rc_dev *dev); int ir_raw_event_register(struct rc_dev *dev); +void ir_raw_event_free(struct rc_dev *dev); void ir_raw_event_unregister(struct rc_dev *dev); int ir_raw_handler_register(struct ir_raw_handler *ir_raw_handler); void ir_raw_handler_unregister(struct ir_raw_handler *ir_raw_handler); diff --git a/drivers/media/rc/rc-ir-raw.c b/drivers/media/rc/rc-ir-raw.c index a2fc1a1d58b0..b6d256f03847 100644 --- a/drivers/media/rc/rc-ir-raw.c +++ b/drivers/media/rc/rc-ir-raw.c @@ -486,15 +486,18 @@ EXPORT_SYMBOL(ir_raw_encode_scancode); /* * Used to (un)register raw event clients */ -int ir_raw_event_register(struct rc_dev *dev) +int ir_raw_event_prepare(struct rc_dev *dev) { - int rc; - struct ir_raw_handler *handler; - struct task_struct *thread; + static bool raw_init; /* 'false' default value, raw decoders loaded? */ if (!dev) return -EINVAL; + if (!raw_init) { + request_module("ir-lirc-codec"); + raw_init = true; + } + dev->raw = kzalloc(sizeof(*dev->raw), GFP_KERNEL); if (!dev->raw) return -ENOMEM; @@ -503,6 +506,14 @@ int ir_raw_event_register(struct rc_dev *dev) dev->change_protocol = change_protocol; INIT_KFIFO(dev->raw->kfifo); + return 0; +} + +int ir_raw_event_register(struct rc_dev *dev) +{ + struct ir_raw_handler *handler; + struct task_struct *thread; + /* * raw transmitters do not need any event registration * because the event is coming from userspace @@ -511,10 +522,8 @@ int ir_raw_event_register(struct rc_dev *dev) thread = kthread_run(ir_raw_event_thread, dev->raw, "rc%u", dev->minor); - if (IS_ERR(thread)) { - rc = PTR_ERR(thread); - goto out; - } + if (IS_ERR(thread)) + return PTR_ERR(thread); dev->raw->thread = thread; } @@ -527,11 +536,15 @@ int ir_raw_event_register(struct rc_dev *dev) mutex_unlock(&ir_raw_handler_lock); return 0; +} + +void ir_raw_event_free(struct rc_dev *dev) +{ + if (!dev) + return; -out: kfree(dev->raw); dev->raw = NULL; - return rc; } void ir_raw_event_unregister(struct rc_dev *dev) @@ -550,8 +563,7 @@ void ir_raw_event_unregister(struct rc_dev *dev) handler->raw_unregister(dev); mutex_unlock(&ir_raw_handler_lock); - kfree(dev->raw); - dev->raw = NULL; + ir_raw_event_free(dev); } /* diff --git a/drivers/media/rc/rc-main.c b/drivers/media/rc/rc-main.c index 6ec73357fa47..a9eba0013525 100644 --- a/drivers/media/rc/rc-main.c +++ b/drivers/media/rc/rc-main.c @@ -15,7 +15,6 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <media/rc-core.h> -#include <linux/atomic.h> #include <linux/spinlock.h> #include <linux/delay.h> #include <linux/input.h> @@ -934,8 +933,8 @@ static bool lirc_is_present(void) * It returns the protocol names of supported protocols. * Enabled protocols are printed in brackets. * - * dev->lock is taken to guard against races between device - * registration, store_protocols and show_protocols. + * dev->lock is taken to guard against races between + * store_protocols and show_protocols. */ static ssize_t show_protocols(struct device *device, struct device_attribute *mattr, char *buf) @@ -945,13 +944,6 @@ static ssize_t show_protocols(struct device *device, char *tmp = buf; int i; - /* Device is being removed */ - if (!dev) - return -EINVAL; - - if (!atomic_read(&dev->initialized)) - return -ERESTARTSYS; - mutex_lock(&dev->lock); enabled = dev->enabled_protocols; @@ -1106,8 +1098,8 @@ static void ir_raw_load_modules(u64 *protocols) * See parse_protocol_change() for the valid commands. * Returns @len on success or a negative error code. * - * dev->lock is taken to guard against races between device - * registration, store_protocols and show_protocols. + * dev->lock is taken to guard against races between + * store_protocols and show_protocols. */ static ssize_t store_protocols(struct device *device, struct device_attribute *mattr, @@ -1119,13 +1111,6 @@ static ssize_t store_protocols(struct device *device, u64 old_protocols, new_protocols; ssize_t rc; - /* Device is being removed */ - if (!dev) - return -EINVAL; - - if (!atomic_read(&dev->initialized)) - return -ERESTARTSYS; - IR_dprintk(1, "Normal protocol change requested\n"); current_protocols = &dev->enabled_protocols; filter = &dev->scancode_filter; @@ -1200,7 +1185,7 @@ out: * Bits of the filter value corresponding to set bits in the filter mask are * compared against input scancodes and non-matching scancodes are discarded. * - * dev->lock is taken to guard against races between device registration, + * dev->lock is taken to guard against races between * store_filter and show_filter. */ static ssize_t show_filter(struct device *device, @@ -1212,13 +1197,6 @@ static ssize_t show_filter(struct device *device, struct rc_scancode_filter *filter; u32 val; - /* Device is being removed */ - if (!dev) - return -EINVAL; - - if (!atomic_read(&dev->initialized)) - return -ERESTARTSYS; - mutex_lock(&dev->lock); if (fattr->type == RC_FILTER_NORMAL) @@ -1251,7 +1229,7 @@ static ssize_t show_filter(struct device *device, * Bits of the filter value corresponding to set bits in the filter mask are * compared against input scancodes and non-matching scancodes are discarded. * - * dev->lock is taken to guard against races between device registration, + * dev->lock is taken to guard against races between * store_filter and show_filter. */ static ssize_t store_filter(struct device *device, @@ -1265,13 +1243,6 @@ static ssize_t store_filter(struct device *device, unsigned long val; int (*set_filter)(struct rc_dev *dev, struct rc_scancode_filter *filter); - /* Device is being removed */ - if (!dev) - return -EINVAL; - - if (!atomic_read(&dev->initialized)) - return -ERESTARTSYS; - ret = kstrtoul(buf, 0, &val); if (ret < 0) return ret; @@ -1372,8 +1343,8 @@ static const char * const proto_variant_names[] = { * It returns the protocol names of supported protocols. * The enabled protocols are printed in brackets. * - * dev->lock is taken to guard against races between device - * registration, store_protocols and show_protocols. + * dev->lock is taken to guard against races between + * store_wakeup_protocols and show_wakeup_protocols. */ static ssize_t show_wakeup_protocols(struct device *device, struct device_attribute *mattr, @@ -1385,13 +1356,6 @@ static ssize_t show_wakeup_protocols(struct device *device, char *tmp = buf; int i; - /* Device is being removed */ - if (!dev) - return -EINVAL; - - if (!atomic_read(&dev->initialized)) - return -ERESTARTSYS; - mutex_lock(&dev->lock); allowed = dev->allowed_wakeup_protocols; @@ -1431,8 +1395,8 @@ static ssize_t show_wakeup_protocols(struct device *device, * It is trigged by writing to /sys/class/rc/rc?/wakeup_protocols. * Returns @len on success or a negative error code. * - * dev->lock is taken to guard against races between device - * registration, store_protocols and show_protocols. + * dev->lock is taken to guard against races between + * store_wakeup_protocols and show_wakeup_protocols. */ static ssize_t store_wakeup_protocols(struct device *device, struct device_attribute *mattr, @@ -1444,13 +1408,6 @@ static ssize_t store_wakeup_protocols(struct device *device, u64 allowed; int i; - /* Device is being removed */ - if (!dev) - return -EINVAL; - - if (!atomic_read(&dev->initialized)) - return -ERESTARTSYS; - mutex_lock(&dev->lock); allowed = dev->allowed_wakeup_protocols; @@ -1663,7 +1620,7 @@ struct rc_dev *devm_rc_allocate_device(struct device *dev, } EXPORT_SYMBOL_GPL(devm_rc_allocate_device); -static int rc_setup_rx_device(struct rc_dev *dev) +static int rc_prepare_rx_device(struct rc_dev *dev) { int rc; struct rc_map *rc_map; @@ -1703,6 +1660,28 @@ static int rc_setup_rx_device(struct rc_dev *dev) if (dev->close) dev->input_dev->close = ir_close; + dev->input_dev->dev.parent = &dev->dev; + memcpy(&dev->input_dev->id, &dev->input_id, sizeof(dev->input_id)); + dev->input_dev->phys = dev->input_phys; + dev->input_dev->name = dev->input_name; + + return 0; + +out_table: + ir_free_table(&dev->rc_map); + + return rc; +} + +static int rc_setup_rx_device(struct rc_dev *dev) +{ + int rc; + + /* rc_open will be called here */ + rc = input_register_device(dev->input_dev); + if (rc) + return rc; + /* * Default delay of 250ms is too short for some protocols, especially * since the timeout is currently set to 250ms. Increase it to 500ms, @@ -1718,38 +1697,24 @@ static int rc_setup_rx_device(struct rc_dev *dev) */ dev->input_dev->rep[REP_PERIOD] = 125; - dev->input_dev->dev.parent = &dev->dev; - memcpy(&dev->input_dev->id, &dev->input_id, sizeof(dev->input_id)); - dev->input_dev->phys = dev->input_phys; - dev->input_dev->name = dev->input_name; - - /* rc_open will be called here */ - rc = input_register_device(dev->input_dev); - if (rc) - goto out_table; - return 0; - -out_table: - ir_free_table(&dev->rc_map); - - return rc; } static void rc_free_rx_device(struct rc_dev *dev) { - if (!dev || dev->driver_type == RC_DRIVER_IR_RAW_TX) + if (!dev) return; - ir_free_table(&dev->rc_map); + if (dev->input_dev) { + input_unregister_device(dev->input_dev); + dev->input_dev = NULL; + } - input_unregister_device(dev->input_dev); - dev->input_dev = NULL; + ir_free_table(&dev->rc_map); } int rc_register_device(struct rc_dev *dev) { - static bool raw_init; /* 'false' default value, raw decoders loaded? */ const char *path; int attr = 0; int minor; @@ -1765,7 +1730,6 @@ int rc_register_device(struct rc_dev *dev) dev->minor = minor; dev_set_name(&dev->dev, "rc%u", dev->minor); dev_set_drvdata(&dev->dev, dev); - atomic_set(&dev->initialized, 0); dev->dev.groups = dev->sysfs_groups; if (dev->driver_type != RC_DRIVER_IR_RAW_TX) @@ -1776,34 +1740,40 @@ int rc_register_device(struct rc_dev *dev) dev->sysfs_groups[attr++] = &rc_dev_wakeup_filter_attr_grp; dev->sysfs_groups[attr++] = NULL; + if (dev->driver_type == RC_DRIVER_IR_RAW || + dev->driver_type == RC_DRIVER_IR_RAW_TX) { + rc = ir_raw_event_prepare(dev); + if (rc < 0) + goto out_minor; + } + + if (dev->driver_type != RC_DRIVER_IR_RAW_TX) { + rc = rc_prepare_rx_device(dev); + if (rc) + goto out_raw; + } + rc = device_add(&dev->dev); if (rc) - goto out_unlock; + goto out_rx_free; path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL); dev_info(&dev->dev, "%s as %s\n", dev->input_name ?: "Unspecified device", path ?: "N/A"); kfree(path); - if (dev->driver_type == RC_DRIVER_IR_RAW || - dev->driver_type == RC_DRIVER_IR_RAW_TX) { - if (!raw_init) { - request_module_nowait("ir-lirc-codec"); - raw_init = true; - } - rc = ir_raw_event_register(dev); - if (rc < 0) - goto out_dev; - } - if (dev->driver_type != RC_DRIVER_IR_RAW_TX) { rc = rc_setup_rx_device(dev); if (rc) - goto out_raw; + goto out_dev; } - /* Allow the RC sysfs nodes to be accessible */ - atomic_set(&dev->initialized, 1); + if (dev->driver_type == RC_DRIVER_IR_RAW || + dev->driver_type == RC_DRIVER_IR_RAW_TX) { + rc = ir_raw_event_register(dev); + if (rc < 0) + goto out_rx; + } IR_dprintk(1, "Registered rc%u (driver: %s)\n", dev->minor, @@ -1811,11 +1781,15 @@ int rc_register_device(struct rc_dev *dev) return 0; -out_raw: - ir_raw_event_unregister(dev); +out_rx: + rc_free_rx_device(dev); out_dev: device_del(&dev->dev); -out_unlock: +out_rx_free: + ir_free_table(&dev->rc_map); +out_raw: + ir_raw_event_free(dev); +out_minor: ida_simple_remove(&rc_ida, minor); return rc; } diff --git a/drivers/media/rc/sir_ir.c b/drivers/media/rc/sir_ir.c index 90a5f8fd5eea..20234ba0b318 100644 --- a/drivers/media/rc/sir_ir.c +++ b/drivers/media/rc/sir_ir.c @@ -53,16 +53,13 @@ static DEFINE_SPINLOCK(hardware_lock); /* Communication with user-space */ static void add_read_queue(int flag, unsigned long val); -static int init_chrdev(void); /* Hardware */ static irqreturn_t sir_interrupt(int irq, void *dev_id); static void send_space(unsigned long len); static void send_pulse(unsigned long len); -static int init_hardware(void); +static void init_hardware(void); static void drop_hardware(void); /* Initialisation */ -static int init_port(void); -static void drop_port(void); static inline unsigned int sinp(int offset) { @@ -122,28 +119,6 @@ static void add_read_queue(int flag, unsigned long val) ir_raw_event_store_with_filter(rcdev, &ev); } -static int init_chrdev(void) -{ - rcdev = devm_rc_allocate_device(&sir_ir_dev->dev, RC_DRIVER_IR_RAW); - if (!rcdev) - return -ENOMEM; - - rcdev->input_name = "SIR IrDA port"; - rcdev->input_phys = KBUILD_MODNAME "/input0"; - rcdev->input_id.bustype = BUS_HOST; - rcdev->input_id.vendor = 0x0001; - rcdev->input_id.product = 0x0001; - rcdev->input_id.version = 0x0100; - rcdev->tx_ir = sir_tx_ir; - rcdev->allowed_protocols = RC_BIT_ALL_IR_DECODER; - rcdev->driver_name = KBUILD_MODNAME; - rcdev->map_name = RC_MAP_RC6_MCE; - rcdev->timeout = IR_DEFAULT_TIMEOUT; - rcdev->dev.parent = &sir_ir_dev->dev; - - return devm_rc_register_device(&sir_ir_dev->dev, rcdev); -} - /* SECTION: Hardware */ static void sir_timeout(unsigned long data) { @@ -288,7 +263,7 @@ static void send_pulse(unsigned long len) } } -static int init_hardware(void) +static void init_hardware(void) { unsigned long flags; @@ -310,7 +285,6 @@ static int init_hardware(void) /* turn on UART */ outb(UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2, io + UART_MCR); spin_unlock_irqrestore(&hardware_lock, flags); - return 0; } static void drop_hardware(void) @@ -326,61 +300,55 @@ static void drop_hardware(void) } /* SECTION: Initialisation */ - -static int init_port(void) +static int sir_ir_probe(struct platform_device *dev) { int retval; + rcdev = devm_rc_allocate_device(&sir_ir_dev->dev, RC_DRIVER_IR_RAW); + if (!rcdev) + return -ENOMEM; + + rcdev->input_name = "SIR IrDA port"; + rcdev->input_phys = KBUILD_MODNAME "/input0"; + rcdev->input_id.bustype = BUS_HOST; + rcdev->input_id.vendor = 0x0001; + rcdev->input_id.product = 0x0001; + rcdev->input_id.version = 0x0100; + rcdev->tx_ir = sir_tx_ir; + rcdev->allowed_protocols = RC_BIT_ALL_IR_DECODER; + rcdev->driver_name = KBUILD_MODNAME; + rcdev->map_name = RC_MAP_RC6_MCE; + rcdev->timeout = IR_DEFAULT_TIMEOUT; + rcdev->dev.parent = &sir_ir_dev->dev; + setup_timer(&timerlist, sir_timeout, 0); /* get I/O port access and IRQ line */ - if (!request_region(io, 8, KBUILD_MODNAME)) { + if (!devm_request_region(&sir_ir_dev->dev, io, 8, KBUILD_MODNAME)) { pr_err("i/o port 0x%.4x already in use.\n", io); return -EBUSY; } - retval = request_irq(irq, sir_interrupt, 0, - KBUILD_MODNAME, NULL); + retval = devm_request_irq(&sir_ir_dev->dev, irq, sir_interrupt, 0, + KBUILD_MODNAME, NULL); if (retval < 0) { - release_region(io, 8); pr_err("IRQ %d already in use.\n", irq); return retval; } pr_info("I/O port 0x%.4x, IRQ %d.\n", io, irq); - return 0; -} - -static void drop_port(void) -{ - free_irq(irq, NULL); - del_timer_sync(&timerlist); - release_region(io, 8); -} - -static int init_sir_ir(void) -{ - int retval; - - retval = init_port(); + retval = devm_rc_register_device(&sir_ir_dev->dev, rcdev); if (retval < 0) return retval; - init_hardware(); - return 0; -} - -static int sir_ir_probe(struct platform_device *dev) -{ - int retval; - retval = init_chrdev(); - if (retval < 0) - return retval; + init_hardware(); - return init_sir_ir(); + return 0; } static int sir_ir_remove(struct platform_device *dev) { + drop_hardware(); + del_timer_sync(&timerlist); return 0; } @@ -421,8 +389,6 @@ pdev_alloc_fail: static void __exit sir_ir_exit(void) { - drop_hardware(); - drop_port(); platform_device_unregister(sir_ir_dev); platform_driver_unregister(&sir_ir_driver); } @@ -434,10 +400,10 @@ MODULE_DESCRIPTION("Infrared receiver driver for SIR type serial ports"); MODULE_AUTHOR("Milan Pikula"); MODULE_LICENSE("GPL"); -module_param(io, int, 0444); +module_param_hw(io, int, ioport, 0444); MODULE_PARM_DESC(io, "I/O address base (0x3f8 or 0x2f8)"); -module_param(irq, int, 0444); +module_param_hw(irq, int, irq, 0444); MODULE_PARM_DESC(irq, "Interrupt (4 or 3)"); module_param(threshold, int, 0444); diff --git a/drivers/media/tuners/tda18271-fe.c b/drivers/media/tuners/tda18271-fe.c index b4e5fa2ff5e5..147155553648 100644 --- a/drivers/media/tuners/tda18271-fe.c +++ b/drivers/media/tuners/tda18271-fe.c @@ -960,7 +960,7 @@ static int tda18271_set_params(struct dvb_frontend *fe) break; case SYS_DVBC_ANNEX_B: bw = 6000000; - /* falltrough */ + /* fall through */ case SYS_DVBC_ANNEX_A: case SYS_DVBC_ANNEX_C: if (bw <= 6000000) { diff --git a/drivers/media/tuners/xc5000.c b/drivers/media/tuners/xc5000.c index e823aafce276..0e7e4fdf9e50 100644 --- a/drivers/media/tuners/xc5000.c +++ b/drivers/media/tuners/xc5000.c @@ -565,38 +565,16 @@ static int xc_get_totalgain(struct xc5000_priv *priv, u16 *totalgain) return xc5000_readreg(priv, XREG_TOTALGAIN, totalgain); } -static u16 wait_for_lock(struct xc5000_priv *priv) -{ - u16 lock_state = 0; - int watch_dog_count = 40; - - while ((lock_state == 0) && (watch_dog_count > 0)) { - xc_get_lock_status(priv, &lock_state); - if (lock_state != 1) { - msleep(5); - watch_dog_count--; - } - } - return lock_state; -} - #define XC_TUNE_ANALOG 0 #define XC_TUNE_DIGITAL 1 static int xc_tune_channel(struct xc5000_priv *priv, u32 freq_hz, int mode) { - int found = 0; - dprintk(1, "%s(%u)\n", __func__, freq_hz); if (xc_set_rf_frequency(priv, freq_hz) != 0) - return 0; - - if (mode == XC_TUNE_ANALOG) { - if (wait_for_lock(priv) == 1) - found = 1; - } + return -EREMOTEIO; - return found; + return 0; } static int xc_set_xtal(struct dvb_frontend *fe) @@ -788,6 +766,7 @@ static int xc5000_set_digital_params(struct dvb_frontend *fe) if (!bw) bw = 6000000; /* fall to OFDM handling */ + /* fall through */ case SYS_DMBTH: case SYS_DVBT: case SYS_DVBT2: diff --git a/drivers/media/usb/au0828/au0828-dvb.c b/drivers/media/usb/au0828/au0828-dvb.c index 7e0c9b795e52..34dc7e062471 100644 --- a/drivers/media/usb/au0828/au0828-dvb.c +++ b/drivers/media/usb/au0828/au0828-dvb.c @@ -105,6 +105,15 @@ static struct tda18271_config hauppauge_woodbury_tunerconfig = { static void au0828_restart_dvb_streaming(struct work_struct *work); +static void au0828_bulk_timeout(unsigned long data) +{ + struct au0828_dev *dev = (struct au0828_dev *) data; + + dprintk(1, "%s called\n", __func__); + dev->bulk_timeout_running = 0; + schedule_work(&dev->restart_streaming); +} + /*-------------------------------------------------------------------*/ static void urb_completion(struct urb *purb) { @@ -138,6 +147,13 @@ static void urb_completion(struct urb *purb) ptr[0], purb->actual_length); schedule_work(&dev->restart_streaming); return; + } else if (dev->bulk_timeout_running == 1) { + /* The URB handler has fired, so cancel timer which would + * restart endpoint if we hadn't + */ + dprintk(1, "%s cancelling bulk timeout\n", __func__); + dev->bulk_timeout_running = 0; + del_timer(&dev->bulk_timeout); } /* Feed the transport payload into the kernel demux */ @@ -160,6 +176,11 @@ static int stop_urb_transfer(struct au0828_dev *dev) if (!dev->urb_streaming) return 0; + if (dev->bulk_timeout_running == 1) { + dev->bulk_timeout_running = 0; + del_timer(&dev->bulk_timeout); + } + dev->urb_streaming = false; for (i = 0; i < URB_COUNT; i++) { if (dev->urbs[i]) { @@ -232,6 +253,11 @@ static int start_urb_transfer(struct au0828_dev *dev) } dev->urb_streaming = true; + + /* If we don't valid data within 1 second, restart stream */ + mod_timer(&dev->bulk_timeout, jiffies + (HZ)); + dev->bulk_timeout_running = 1; + return 0; } @@ -622,6 +648,10 @@ int au0828_dvb_register(struct au0828_dev *dev) return ret; } + dev->bulk_timeout.function = au0828_bulk_timeout; + dev->bulk_timeout.data = (unsigned long) dev; + init_timer(&dev->bulk_timeout); + return 0; } diff --git a/drivers/media/usb/au0828/au0828.h b/drivers/media/usb/au0828/au0828.h index 88e59748ebc2..05e445fe0b77 100644 --- a/drivers/media/usb/au0828/au0828.h +++ b/drivers/media/usb/au0828/au0828.h @@ -195,6 +195,8 @@ struct au0828_dev { /* Digital */ struct au0828_dvb dvb; struct work_struct restart_streaming; + struct timer_list bulk_timeout; + int bulk_timeout_running; #ifdef CONFIG_VIDEO_AU0828_V4L2 /* Analog */ diff --git a/drivers/media/usb/cpia2/cpia2_core.c b/drivers/media/usb/cpia2/cpia2_core.c index b1d13444ff30..0efba0da0a45 100644 --- a/drivers/media/usb/cpia2/cpia2_core.c +++ b/drivers/media/usb/cpia2/cpia2_core.c @@ -173,7 +173,8 @@ int cpia2_do_command(struct camera_data *cam, cmd.start = CPIA2_VP_DEVICEH; break; case CPIA2_CMD_SET_VP_BRIGHTNESS: - cmd.buffer.block_data[0] = param; /* Then fall through */ + cmd.buffer.block_data[0] = param; + /* fall through */ case CPIA2_CMD_GET_VP_BRIGHTNESS: cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VP; cmd.reg_count = 1; @@ -183,14 +184,16 @@ int cpia2_do_command(struct camera_data *cam, cmd.start = CPIA2_VP5_EXPOSURE_TARGET; break; case CPIA2_CMD_SET_CONTRAST: - cmd.buffer.block_data[0] = param; /* Then fall through */ + cmd.buffer.block_data[0] = param; + /* fall through */ case CPIA2_CMD_GET_CONTRAST: cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VP; cmd.reg_count = 1; cmd.start = CPIA2_VP_YRANGE; break; case CPIA2_CMD_SET_VP_SATURATION: - cmd.buffer.block_data[0] = param; /* Then fall through */ + cmd.buffer.block_data[0] = param; + /* fall through */ case CPIA2_CMD_GET_VP_SATURATION: cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VP; cmd.reg_count = 1; @@ -200,28 +203,32 @@ int cpia2_do_command(struct camera_data *cam, cmd.start = CPIA2_VP5_MCUVSATURATION; break; case CPIA2_CMD_SET_VP_GPIO_DATA: - cmd.buffer.block_data[0] = param; /* Then fall through */ + cmd.buffer.block_data[0] = param; + /* fall through */ case CPIA2_CMD_GET_VP_GPIO_DATA: cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VP; cmd.reg_count = 1; cmd.start = CPIA2_VP_GPIO_DATA; break; case CPIA2_CMD_SET_VP_GPIO_DIRECTION: - cmd.buffer.block_data[0] = param; /* Then fall through */ + cmd.buffer.block_data[0] = param; + /* fall through */ case CPIA2_CMD_GET_VP_GPIO_DIRECTION: cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VP; cmd.reg_count = 1; cmd.start = CPIA2_VP_GPIO_DIRECTION; break; case CPIA2_CMD_SET_VC_MP_GPIO_DATA: - cmd.buffer.block_data[0] = param; /* Then fall through */ + cmd.buffer.block_data[0] = param; + /* fall through */ case CPIA2_CMD_GET_VC_MP_GPIO_DATA: cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VC; cmd.reg_count = 1; cmd.start = CPIA2_VC_MP_DATA; break; case CPIA2_CMD_SET_VC_MP_GPIO_DIRECTION: - cmd.buffer.block_data[0] = param; /* Then fall through */ + cmd.buffer.block_data[0] = param; + /*fall through */ case CPIA2_CMD_GET_VC_MP_GPIO_DIRECTION: cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VC; cmd.reg_count = 1; @@ -235,7 +242,8 @@ int cpia2_do_command(struct camera_data *cam, cmd.buffer.block_data[0] = param; break; case CPIA2_CMD_SET_FLICKER_MODES: - cmd.buffer.block_data[0] = param; /* Then fall through */ + cmd.buffer.block_data[0] = param; + /* fall through */ case CPIA2_CMD_GET_FLICKER_MODES: cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VP; cmd.reg_count = 1; @@ -280,8 +288,9 @@ int cpia2_do_command(struct camera_data *cam, cmd.start = CPIA2_SYSTEM_SYSTEM_CONTROL; cmd.buffer.block_data[0] = CPIA2_SYSTEM_CONTROL_CLEAR_ERR; break; - case CPIA2_CMD_SET_USER_MODE: /* Then fall through */ + case CPIA2_CMD_SET_USER_MODE: cmd.buffer.block_data[0] = param; + /* fall through */ case CPIA2_CMD_GET_USER_MODE: cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VP; cmd.reg_count = 1; @@ -300,14 +309,16 @@ int cpia2_do_command(struct camera_data *cam, cmd.buffer.block_data[0] = param; break; case CPIA2_CMD_SET_WAKEUP: - cmd.buffer.block_data[0] = param; /* Then fall through */ + cmd.buffer.block_data[0] = param; + /* fall through */ case CPIA2_CMD_GET_WAKEUP: cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VC; cmd.reg_count = 1; cmd.start = CPIA2_VC_WAKEUP; break; case CPIA2_CMD_SET_PW_CONTROL: - cmd.buffer.block_data[0] = param; /* Then fall through */ + cmd.buffer.block_data[0] = param; + /* fall through */ case CPIA2_CMD_GET_PW_CONTROL: cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VC; cmd.reg_count = 1; @@ -319,7 +330,8 @@ int cpia2_do_command(struct camera_data *cam, cmd.start = CPIA2_VP_SYSTEMSTATE; break; case CPIA2_CMD_SET_SYSTEM_CTRL: - cmd.buffer.block_data[0] = param; /* Then fall through */ + cmd.buffer.block_data[0] = param; + /* fall through */ case CPIA2_CMD_GET_SYSTEM_CTRL: cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_SYSTEM; @@ -327,21 +339,24 @@ int cpia2_do_command(struct camera_data *cam, cmd.start = CPIA2_SYSTEM_SYSTEM_CONTROL; break; case CPIA2_CMD_SET_VP_SYSTEM_CTRL: - cmd.buffer.block_data[0] = param; /* Then fall through */ + cmd.buffer.block_data[0] = param; + /* fall through */ case CPIA2_CMD_GET_VP_SYSTEM_CTRL: cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VP; cmd.reg_count = 1; cmd.start = CPIA2_VP_SYSTEMCTRL; break; case CPIA2_CMD_SET_VP_EXP_MODES: - cmd.buffer.block_data[0] = param; /* Then fall through */ + cmd.buffer.block_data[0] = param; + /* fall through */ case CPIA2_CMD_GET_VP_EXP_MODES: cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VP; cmd.reg_count = 1; cmd.start = CPIA2_VP_EXPOSURE_MODES; break; case CPIA2_CMD_SET_DEVICE_CONFIG: - cmd.buffer.block_data[0] = param; /* Then fall through */ + cmd.buffer.block_data[0] = param; + /* fall through */ case CPIA2_CMD_GET_DEVICE_CONFIG: cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VP; cmd.reg_count = 1; @@ -361,7 +376,8 @@ int cpia2_do_command(struct camera_data *cam, cmd.start = CPIA2_SENSOR_CR1; break; case CPIA2_CMD_SET_VC_CONTROL: - cmd.buffer.block_data[0] = param; /* Then fall through */ + cmd.buffer.block_data[0] = param; + /* fall through */ case CPIA2_CMD_GET_VC_CONTROL: cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VC; cmd.reg_count = 1; @@ -395,7 +411,8 @@ int cpia2_do_command(struct camera_data *cam, case CPIA2_CMD_SET_USER_EFFECTS: /* Note: Be careful with this as this register can also affect flicker modes */ - cmd.buffer.block_data[0] = param; /* Then fall through */ + cmd.buffer.block_data[0] = param; + /* fall through */ case CPIA2_CMD_GET_USER_EFFECTS: cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VP; cmd.reg_count = 1; diff --git a/drivers/media/usb/cx231xx/Kconfig b/drivers/media/usb/cx231xx/Kconfig index 58de80bff4c7..6276d9b2198b 100644 --- a/drivers/media/usb/cx231xx/Kconfig +++ b/drivers/media/usb/cx231xx/Kconfig @@ -52,6 +52,8 @@ config VIDEO_CX231XX_DVB select DVB_SI2165 if MEDIA_SUBDRV_AUTOSELECT select DVB_SI2168 if MEDIA_SUBDRV_AUTOSELECT select MEDIA_TUNER_SI2157 if MEDIA_SUBDRV_AUTOSELECT + select DVB_MN88473 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_R820T if MEDIA_SUBDRV_AUTOSELECT ---help--- This adds support for DVB cards based on the diff --git a/drivers/media/usb/cx231xx/cx231xx-cards.c b/drivers/media/usb/cx231xx/cx231xx-cards.c index a1007d005290..e0daa9b6c2a0 100644 --- a/drivers/media/usb/cx231xx/cx231xx-cards.c +++ b/drivers/media/usb/cx231xx/cx231xx-cards.c @@ -868,6 +868,33 @@ struct cx231xx_board cx231xx_boards[] = { .amux = CX231XX_AMUX_LINE_IN, } }, }, + [CX231XX_BOARD_ASTROMETA_T2HYBRID] = { + .name = "Astrometa T2hybrid", + .tuner_type = TUNER_ABSENT, + .has_dvb = 1, + .output_mode = OUT_MODE_VIP11, + .agc_analog_digital_select_gpio = 0x01, + .ctl_pin_status_mask = 0xffffffc4, + .demod_addr = 0x18, /* 0x30 >> 1 */ + .demod_i2c_master = I2C_1_MUX_1, + .gpio_pin_status_mask = 0xa, + .norm = V4L2_STD_NTSC, + .tuner_addr = 0x3a, /* 0x74 >> 1 */ + .tuner_i2c_master = I2C_1_MUX_3, + .tuner_scl_gpio = 0x1a, + .tuner_sda_gpio = 0x1b, + .tuner_sif_gpio = 0x05, + .input = {{ + .type = CX231XX_VMUX_TELEVISION, + .vmux = CX231XX_VIN_1_1, + .amux = CX231XX_AMUX_VIDEO, + }, { + .type = CX231XX_VMUX_COMPOSITE1, + .vmux = CX231XX_VIN_2_1, + .amux = CX231XX_AMUX_LINE_IN, + }, + }, + }, }; const unsigned int cx231xx_bcount = ARRAY_SIZE(cx231xx_boards); @@ -937,6 +964,8 @@ struct usb_device_id cx231xx_id_table[] = { .driver_info = CX231XX_BOARD_TERRATEC_GRABBY}, {USB_DEVICE(0x1b80, 0xd3b2), .driver_info = CX231XX_BOARD_EVROMEDIA_FULL_HYBRID_FULLHD}, + {USB_DEVICE(0x15f4, 0x0135), + .driver_info = CX231XX_BOARD_ASTROMETA_T2HYBRID}, {}, }; @@ -1013,6 +1042,11 @@ void cx231xx_pre_card_setup(struct cx231xx *dev) dev_info(dev->dev, "Identified as %s (card=%d)\n", dev->board.name, dev->model); + if (CX231XX_BOARD_ASTROMETA_T2HYBRID == dev->model) { + /* turn on demodulator chip */ + cx231xx_set_gpio_value(dev, 0x03, 0x01); + } + /* set the direction for GPIO pins */ if (dev->board.tuner_gpio) { cx231xx_set_gpio_direction(dev, dev->board.tuner_gpio->bit, 1); diff --git a/drivers/media/usb/cx231xx/cx231xx-dvb.c b/drivers/media/usb/cx231xx/cx231xx-dvb.c index 46427fd3b220..ee3eeeb600f8 100644 --- a/drivers/media/usb/cx231xx/cx231xx-dvb.c +++ b/drivers/media/usb/cx231xx/cx231xx-dvb.c @@ -37,6 +37,8 @@ #include "mb86a20s.h" #include "si2157.h" #include "lgdt3306a.h" +#include "r820t.h" +#include "mn88473.h" MODULE_DESCRIPTION("driver for cx231xx based DVB cards"); MODULE_AUTHOR("Srinivasa Deevi <srinivasa.deevi@conexant.com>"); @@ -164,6 +166,13 @@ static struct lgdt3306a_config hauppauge_955q_lgdt3306a_config = { .xtalMHz = 25, }; +static struct r820t_config astrometa_t2hybrid_r820t_config = { + .i2c_addr = 0x3a, /* 0x74 >> 1 */ + .xtal = 16000000, + .rafael_chip = CHIP_R828D, + .max_i2c_msg_len = 2, +}; + static inline void print_err_status(struct cx231xx *dev, int packet, int status) { char *errmsg = "Unknown"; @@ -1019,6 +1028,46 @@ static int dvb_init(struct cx231xx *dev) dev->dvb->i2c_client_tuner = client; break; } + case CX231XX_BOARD_ASTROMETA_T2HYBRID: + { + struct i2c_client *client; + struct i2c_board_info info = {}; + struct mn88473_config mn88473_config = {}; + + /* attach demodulator chip */ + mn88473_config.i2c_wr_max = 16; + mn88473_config.xtal = 25000000; + mn88473_config.fe = &dev->dvb->frontend; + + strlcpy(info.type, "mn88473", sizeof(info.type)); + info.addr = dev->board.demod_addr; + info.platform_data = &mn88473_config; + + request_module(info.type); + client = i2c_new_device(demod_i2c, &info); + + if (client == NULL || client->dev.driver == NULL) { + result = -ENODEV; + goto out_free; + } + + if (!try_module_get(client->dev.driver->owner)) { + i2c_unregister_device(client); + result = -ENODEV; + goto out_free; + } + + dvb->i2c_client_demod = client; + + /* define general-purpose callback pointer */ + dvb->frontend->callback = cx231xx_tuner_callback; + + /* attach tuner chip */ + dvb_attach(r820t_attach, dev->dvb->frontend, + tuner_i2c, + &astrometa_t2hybrid_r820t_config); + break; + } default: dev_err(dev->dev, "%s/2: The frontend of your DVB/ATSC card isn't supported yet\n", diff --git a/drivers/media/usb/cx231xx/cx231xx-input.c b/drivers/media/usb/cx231xx/cx231xx-input.c index 6e80f3c573f3..eecf074b0a48 100644 --- a/drivers/media/usb/cx231xx/cx231xx-input.c +++ b/drivers/media/usb/cx231xx/cx231xx-input.c @@ -30,7 +30,7 @@ static int get_key_isdbt(struct IR_i2c *ir, enum rc_type *protocol, int rc; u8 cmd, scancode; - dev_dbg(&ir->rc->input_dev->dev, "%s\n", __func__); + dev_dbg(&ir->rc->dev, "%s\n", __func__); /* poll IR chip */ rc = i2c_master_recv(ir->c, &cmd, 1); @@ -48,8 +48,7 @@ static int get_key_isdbt(struct IR_i2c *ir, enum rc_type *protocol, scancode = bitrev8(cmd); - dev_dbg(&ir->rc->input_dev->dev, "cmd %02x, scan = %02x\n", - cmd, scancode); + dev_dbg(&ir->rc->dev, "cmd %02x, scan = %02x\n", cmd, scancode); *protocol = RC_TYPE_OTHER; *pscancode = scancode; diff --git a/drivers/media/usb/cx231xx/cx231xx-video.c b/drivers/media/usb/cx231xx/cx231xx-video.c index 6414188ffdfa..f67f86876625 100644 --- a/drivers/media/usb/cx231xx/cx231xx-video.c +++ b/drivers/media/usb/cx231xx/cx231xx-video.c @@ -1134,7 +1134,7 @@ void cx231xx_v4l2_create_entities(struct cx231xx *dev) /* The DVB core will handle it */ if (dev->tuner_type == TUNER_ABSENT) continue; - /* fall though */ + /* fall through */ default: /* just to shut up a gcc warning */ ent->function = MEDIA_ENT_F_CONN_RF; break; diff --git a/drivers/media/usb/cx231xx/cx231xx.h b/drivers/media/usb/cx231xx/cx231xx.h index d9792ea4bbc6..986c64ba5b56 100644 --- a/drivers/media/usb/cx231xx/cx231xx.h +++ b/drivers/media/usb/cx231xx/cx231xx.h @@ -79,6 +79,7 @@ #define CX231XX_BOARD_HAUPPAUGE_955Q 21 #define CX231XX_BOARD_TERRATEC_GRABBY 22 #define CX231XX_BOARD_EVROMEDIA_FULL_HYBRID_FULLHD 23 +#define CX231XX_BOARD_ASTROMETA_T2HYBRID 24 /* Limits minimum and default number of buffers */ #define CX231XX_MIN_BUF 4 diff --git a/drivers/media/usb/dvb-usb-v2/af9015.c b/drivers/media/usb/dvb-usb-v2/af9015.c index caa1e6101f58..612431ab0fb3 100644 --- a/drivers/media/usb/dvb-usb-v2/af9015.c +++ b/drivers/media/usb/dvb-usb-v2/af9015.c @@ -52,6 +52,7 @@ static int af9015_ctrl_msg(struct dvb_usb_device *d, struct req_t *req) case READ_I2C: write = 0; state->buf[2] |= 0x01; /* set I2C direction */ + /* fall through */ case WRITE_I2C: state->buf[0] = READ_WRITE_I2C; break; diff --git a/drivers/media/usb/dvb-usb-v2/lmedm04.c b/drivers/media/usb/dvb-usb-v2/lmedm04.c index 924adfdb660d..594360a63c18 100644 --- a/drivers/media/usb/dvb-usb-v2/lmedm04.c +++ b/drivers/media/usb/dvb-usb-v2/lmedm04.c @@ -1065,6 +1065,7 @@ static int dm04_lme2510_frontend_attach(struct dvb_usb_adapter *adap) } break; } + /* fall through */ case 0x22f0: st->i2c_gate = 5; adap->fe[0] = dvb_attach(m88rs2000_attach, diff --git a/drivers/media/usb/dvb-usb-v2/mxl111sf-i2c.c b/drivers/media/usb/dvb-usb-v2/mxl111sf-i2c.c index ffb49c28b15a..0eb33e043079 100644 --- a/drivers/media/usb/dvb-usb-v2/mxl111sf-i2c.c +++ b/drivers/media/usb/dvb-usb-v2/mxl111sf-i2c.c @@ -316,7 +316,7 @@ fail: static int mxl111sf_i2c_send_data(struct mxl111sf_state *state, u8 index, u8 *wdata) { - int ret = mxl111sf_ctrl_msg(state->d, wdata[0], + int ret = mxl111sf_ctrl_msg(state, wdata[0], &wdata[1], 25, NULL, 0); mxl_fail(ret); @@ -326,7 +326,7 @@ static int mxl111sf_i2c_send_data(struct mxl111sf_state *state, static int mxl111sf_i2c_get_data(struct mxl111sf_state *state, u8 index, u8 *wdata, u8 *rdata) { - int ret = mxl111sf_ctrl_msg(state->d, wdata[0], + int ret = mxl111sf_ctrl_msg(state, wdata[0], &wdata[1], 25, rdata, 24); mxl_fail(ret); diff --git a/drivers/media/usb/dvb-usb-v2/mxl111sf.c b/drivers/media/usb/dvb-usb-v2/mxl111sf.c index abf69d8fa469..b0d5904a4ea6 100644 --- a/drivers/media/usb/dvb-usb-v2/mxl111sf.c +++ b/drivers/media/usb/dvb-usb-v2/mxl111sf.c @@ -24,9 +24,6 @@ #include "lgdt3305.h" #include "lg2160.h" -/* Max transfer size done by I2C transfer functions */ -#define MAX_XFER_SIZE 64 - int dvb_usb_mxl111sf_debug; module_param_named(debug, dvb_usb_mxl111sf_debug, int, 0644); MODULE_PARM_DESC(debug, "set debugging level (1=info, 2=xfer, 4=i2c, 8=reg, 16=adv (or-able))."); @@ -55,27 +52,34 @@ MODULE_PARM_DESC(rfswitch, "force rf switch position (0=auto, 1=ext, 2=int)."); DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); -int mxl111sf_ctrl_msg(struct dvb_usb_device *d, +int mxl111sf_ctrl_msg(struct mxl111sf_state *state, u8 cmd, u8 *wbuf, int wlen, u8 *rbuf, int rlen) { + struct dvb_usb_device *d = state->d; int wo = (rbuf == NULL || rlen == 0); /* write-only */ int ret; - u8 sndbuf[MAX_XFER_SIZE]; - if (1 + wlen > sizeof(sndbuf)) { + if (1 + wlen > MXL_MAX_XFER_SIZE) { pr_warn("%s: len=%d is too big!\n", __func__, wlen); return -EOPNOTSUPP; } pr_debug("%s(wlen = %d, rlen = %d)\n", __func__, wlen, rlen); - memset(sndbuf, 0, 1+wlen); + mutex_lock(&state->msg_lock); + memset(state->sndbuf, 0, 1+wlen); + memset(state->rcvbuf, 0, rlen); + + state->sndbuf[0] = cmd; + memcpy(&state->sndbuf[1], wbuf, wlen); - sndbuf[0] = cmd; - memcpy(&sndbuf[1], wbuf, wlen); + ret = (wo) ? dvb_usbv2_generic_write(d, state->sndbuf, 1+wlen) : + dvb_usbv2_generic_rw(d, state->sndbuf, 1+wlen, state->rcvbuf, + rlen); + + memcpy(rbuf, state->rcvbuf, rlen); + mutex_unlock(&state->msg_lock); - ret = (wo) ? dvb_usbv2_generic_write(d, sndbuf, 1+wlen) : - dvb_usbv2_generic_rw(d, sndbuf, 1+wlen, rbuf, rlen); mxl_fail(ret); return ret; @@ -91,7 +95,7 @@ int mxl111sf_read_reg(struct mxl111sf_state *state, u8 addr, u8 *data) u8 buf[2]; int ret; - ret = mxl111sf_ctrl_msg(state->d, MXL_CMD_REG_READ, &addr, 1, buf, 2); + ret = mxl111sf_ctrl_msg(state, MXL_CMD_REG_READ, &addr, 1, buf, 2); if (mxl_fail(ret)) { mxl_debug("error reading reg: 0x%02x", addr); goto fail; @@ -117,7 +121,7 @@ int mxl111sf_write_reg(struct mxl111sf_state *state, u8 addr, u8 data) pr_debug("W: (0x%02x, 0x%02x)\n", addr, data); - ret = mxl111sf_ctrl_msg(state->d, MXL_CMD_REG_WRITE, buf, 2, NULL, 0); + ret = mxl111sf_ctrl_msg(state, MXL_CMD_REG_WRITE, buf, 2, NULL, 0); if (mxl_fail(ret)) pr_err("error writing reg: 0x%02x, val: 0x%02x", addr, data); return ret; @@ -926,6 +930,8 @@ static int mxl111sf_init(struct dvb_usb_device *d) .len = sizeof(eeprom), .buf = eeprom }, }; + mutex_init(&state->msg_lock); + ret = get_chip_info(state); if (mxl_fail(ret)) pr_err("failed to get chip info during probe"); diff --git a/drivers/media/usb/dvb-usb-v2/mxl111sf.h b/drivers/media/usb/dvb-usb-v2/mxl111sf.h index 846260e0eec0..3e6f5880bd1e 100644 --- a/drivers/media/usb/dvb-usb-v2/mxl111sf.h +++ b/drivers/media/usb/dvb-usb-v2/mxl111sf.h @@ -19,6 +19,9 @@ #include <media/tveeprom.h> #include <media/media-entity.h> +/* Max transfer size done by I2C transfer functions */ +#define MXL_MAX_XFER_SIZE 64 + #define MXL_EP1_REG_READ 1 #define MXL_EP2_REG_WRITE 2 #define MXL_EP3_INTERRUPT 3 @@ -86,6 +89,9 @@ struct mxl111sf_state { struct mutex fe_lock; u8 num_frontends; struct mxl111sf_adap_state adap_state[3]; + u8 sndbuf[MXL_MAX_XFER_SIZE]; + u8 rcvbuf[MXL_MAX_XFER_SIZE]; + struct mutex msg_lock; #ifdef CONFIG_MEDIA_CONTROLLER_DVB struct media_entity tuner; struct media_pad tuner_pads[2]; @@ -108,7 +114,7 @@ int mxl111sf_ctrl_program_regs(struct mxl111sf_state *state, /* needed for hardware i2c functions in mxl111sf-i2c.c: * mxl111sf_i2c_send_data / mxl111sf_i2c_get_data */ -int mxl111sf_ctrl_msg(struct dvb_usb_device *d, +int mxl111sf_ctrl_msg(struct mxl111sf_state *state, u8 cmd, u8 *wbuf, int wlen, u8 *rbuf, int rlen); #define mxl_printk(kern, fmt, arg...) \ diff --git a/drivers/media/usb/dvb-usb/dib0700_devices.c b/drivers/media/usb/dvb-usb/dib0700_devices.c index 85ab3fa48f9a..6a57fc6d3472 100644 --- a/drivers/media/usb/dvb-usb/dib0700_devices.c +++ b/drivers/media/usb/dvb-usb/dib0700_devices.c @@ -1659,6 +1659,7 @@ static int dib8096_set_param_override(struct dvb_frontend *fe) switch (band) { default: deb_info("Warning : Rf frequency (%iHz) is not in the supported range, using VHF switch ", fe->dtv_property_cache.frequency); + /* fall through */ case BAND_VHF: state->dib8000_ops.set_gpio(fe, 3, 0, 1); break; diff --git a/drivers/media/usb/dvb-usb/dvb-usb-remote.c b/drivers/media/usb/dvb-usb/dvb-usb-remote.c index 059ded59208e..f05f1fc80729 100644 --- a/drivers/media/usb/dvb-usb/dvb-usb-remote.c +++ b/drivers/media/usb/dvb-usb/dvb-usb-remote.c @@ -131,6 +131,11 @@ static void legacy_dvb_usb_read_remote_control(struct work_struct *work) case REMOTE_KEY_PRESSED: deb_rc("key pressed\n"); d->last_event = event; + input_event(d->input_dev, EV_KEY, event, 1); + input_sync(d->input_dev); + input_event(d->input_dev, EV_KEY, d->last_event, 0); + input_sync(d->input_dev); + break; case REMOTE_KEY_REPEAT: deb_rc("key repeated\n"); input_event(d->input_dev, EV_KEY, event, 1); diff --git a/drivers/media/usb/dvb-usb/dw2102.c b/drivers/media/usb/dvb-usb/dw2102.c index 6e654e5026dd..57b187240110 100644 --- a/drivers/media/usb/dvb-usb/dw2102.c +++ b/drivers/media/usb/dvb-usb/dw2102.c @@ -1840,11 +1840,12 @@ static int dw2102_load_firmware(struct usb_device *dev, switch (le16_to_cpu(dev->descriptor.idProduct)) { case USB_PID_TEVII_S650: dw2104_properties.rc.core.rc_codes = RC_MAP_TEVII_NEC; + /* fall through */ case USB_PID_DW2104: reset = 1; dw210x_op_rw(dev, 0xc4, 0x0000, 0, &reset, 1, DW210X_WRITE_MSG); - /* break omitted intentionally */ + /* fall through */ case USB_PID_DW3101: reset = 0; dw210x_op_rw(dev, 0xbf, 0x0040, 0, &reset, 0, @@ -1877,6 +1878,7 @@ static int dw2102_load_firmware(struct usb_device *dev, break; } } + /* fall through */ case 0x2101: dw210x_op_rw(dev, 0xbc, 0x0030, 0, &reset16[0], 2, DW210X_READ_MSG); diff --git a/drivers/media/usb/em28xx/em28xx-cards.c b/drivers/media/usb/em28xx/em28xx-cards.c index a12b599a1fa2..146341aeb782 100644 --- a/drivers/media/usb/em28xx/em28xx-cards.c +++ b/drivers/media/usb/em28xx/em28xx-cards.c @@ -2855,7 +2855,7 @@ static int em28xx_hint_board(struct em28xx *dev) "Your board has no unique USB ID.\n" "A hint were successfully done, based on eeprom hash.\n" "This method is not 100%% failproof.\n" - "If the board were missdetected, please email this log to:\n" + "If the board were misdetected, please email this log to:\n" "\tV4L Mailing List <linux-media@vger.kernel.org>\n" "Board detected as %s\n", em28xx_boards[dev->model].name); @@ -2885,7 +2885,7 @@ static int em28xx_hint_board(struct em28xx *dev) "Your board has no unique USB ID.\n" "A hint were successfully done, based on i2c devicelist hash.\n" "This method is not 100%% failproof.\n" - "If the board were missdetected, please email this log to:\n" + "If the board were misdetected, please email this log to:\n" "\tV4L Mailing List <linux-media@vger.kernel.org>\n" "Board detected as %s\n", em28xx_boards[dev->model].name); diff --git a/drivers/media/usb/em28xx/em28xx-core.c b/drivers/media/usb/em28xx/em28xx-core.c index 19ccff41c7eb..1d0d8cc06103 100644 --- a/drivers/media/usb/em28xx/em28xx-core.c +++ b/drivers/media/usb/em28xx/em28xx-core.c @@ -91,22 +91,16 @@ int em28xx_read_reg_req_len(struct em28xx *dev, u8 req, u16 reg, if (len > URB_MAX_CTRL_SIZE) return -EINVAL; - em28xx_regdbg("(pipe 0x%08x): IN: %02x %02x %02x %02x %02x %02x %02x %02x ", - pipe, USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, - req, 0, 0, - reg & 0xff, reg >> 8, - len & 0xff, len >> 8); - mutex_lock(&dev->ctrl_urb_lock); ret = usb_control_msg(udev, pipe, req, USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, 0x0000, reg, dev->urb_buf, len, HZ); if (ret < 0) { - em28xx_regdbg("(pipe 0x%08x): IN: %02x %02x %02x %02x %02x %02x %02x %02x failed\n", + em28xx_regdbg("(pipe 0x%08x): IN: %02x %02x %02x %02x %02x %02x %02x %02x failed with error %i\n", pipe, USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, req, 0, 0, reg & 0xff, reg >> 8, - len & 0xff, len >> 8); + len & 0xff, len >> 8, ret); mutex_unlock(&dev->ctrl_urb_lock); return usb_translate_errors(ret); } @@ -116,7 +110,7 @@ int em28xx_read_reg_req_len(struct em28xx *dev, u8 req, u16 reg, mutex_unlock(&dev->ctrl_urb_lock); - em28xx_regdbg("(pipe 0x%08x): IN: %02x %02x %02x %02x %02x %02x %02x %02x failed <<< %*ph\n", + em28xx_regdbg("(pipe 0x%08x): IN: %02x %02x %02x %02x %02x %02x %02x %02x <<< %*ph\n", pipe, USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, req, 0, 0, reg & 0xff, reg >> 8, @@ -164,13 +158,6 @@ int em28xx_write_regs_req(struct em28xx *dev, u8 req, u16 reg, char *buf, if ((len < 1) || (len > URB_MAX_CTRL_SIZE)) return -EINVAL; - em28xx_regdbg("(pipe 0x%08x): OUT: %02x %02x %02x %02x %02x %02x %02x %02x >>> %*ph\n", - pipe, - USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, - req, 0, 0, - reg & 0xff, reg >> 8, - len & 0xff, len >> 8, len, buf); - mutex_lock(&dev->ctrl_urb_lock); memcpy(dev->urb_buf, buf, len); ret = usb_control_msg(udev, pipe, req, @@ -178,8 +165,22 @@ int em28xx_write_regs_req(struct em28xx *dev, u8 req, u16 reg, char *buf, 0x0000, reg, dev->urb_buf, len, HZ); mutex_unlock(&dev->ctrl_urb_lock); - if (ret < 0) + if (ret < 0) { + em28xx_regdbg("(pipe 0x%08x): OUT: %02x %02x %02x %02x %02x %02x %02x %02x >>> %*ph failed with error %i\n", + pipe, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + req, 0, 0, + reg & 0xff, reg >> 8, + len & 0xff, len >> 8, len, buf, ret); return usb_translate_errors(ret); + } + + em28xx_regdbg("(pipe 0x%08x): OUT: %02x %02x %02x %02x %02x %02x %02x %02x >>> %*ph\n", + pipe, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + req, 0, 0, + reg & 0xff, reg >> 8, + len & 0xff, len >> 8, len, buf); if (dev->wait_after_write) msleep(dev->wait_after_write); diff --git a/drivers/media/usb/gspca/m5602/m5602_s5k83a.c b/drivers/media/usb/gspca/m5602/m5602_s5k83a.c index be5e25d1a2e8..6ad8d4849680 100644 --- a/drivers/media/usb/gspca/m5602/m5602_s5k83a.c +++ b/drivers/media/usb/gspca/m5602/m5602_s5k83a.c @@ -345,6 +345,11 @@ int s5k83a_start(struct sd *sd) to assume that there is no better way of accomplishing this */ sd->rotation_thread = kthread_create(rotation_thread_function, sd, "rotation thread"); + if (IS_ERR(sd->rotation_thread)) { + err = PTR_ERR(sd->rotation_thread); + sd->rotation_thread = NULL; + return err; + } wake_up_process(sd->rotation_thread); /* Preinit the sensor */ diff --git a/drivers/media/usb/gspca/ov519.c b/drivers/media/usb/gspca/ov519.c index f4c41f043cda..cdb79c5f0c38 100644 --- a/drivers/media/usb/gspca/ov519.c +++ b/drivers/media/usb/gspca/ov519.c @@ -3526,7 +3526,8 @@ static void ov511_mode_init_regs(struct sd *sd) sd->clockdiv = 0; break; } - /* Fall through for 640x480 case */ + /* For 640x480 case */ + /* fall through */ default: /* case 20: */ /* case 15: */ diff --git a/drivers/media/usb/pulse8-cec/pulse8-cec.c b/drivers/media/usb/pulse8-cec/pulse8-cec.c index 1dfc2de1fe77..c843070f24c1 100644 --- a/drivers/media/usb/pulse8-cec/pulse8-cec.c +++ b/drivers/media/usb/pulse8-cec/pulse8-cec.c @@ -148,18 +148,15 @@ static void pulse8_irq_work_handler(struct work_struct *work) cec_received_msg(pulse8->adap, &pulse8->rx_msg); break; case MSGCODE_TRANSMIT_SUCCEEDED: - cec_transmit_done(pulse8->adap, CEC_TX_STATUS_OK, - 0, 0, 0, 0); + cec_transmit_attempt_done(pulse8->adap, CEC_TX_STATUS_OK); break; case MSGCODE_TRANSMIT_FAILED_ACK: - cec_transmit_done(pulse8->adap, CEC_TX_STATUS_NACK, - 0, 1, 0, 0); + cec_transmit_attempt_done(pulse8->adap, CEC_TX_STATUS_NACK); break; case MSGCODE_TRANSMIT_FAILED_LINE: case MSGCODE_TRANSMIT_FAILED_TIMEOUT_DATA: case MSGCODE_TRANSMIT_FAILED_TIMEOUT_LINE: - cec_transmit_done(pulse8->adap, CEC_TX_STATUS_ERROR, - 0, 0, 0, 1); + cec_transmit_attempt_done(pulse8->adap, CEC_TX_STATUS_ERROR); break; } } diff --git a/drivers/media/usb/pvrusb2/pvrusb2-i2c-core.c b/drivers/media/usb/pvrusb2/pvrusb2-i2c-core.c index f727b54a53c6..20a52b785fff 100644 --- a/drivers/media/usb/pvrusb2/pvrusb2-i2c-core.c +++ b/drivers/media/usb/pvrusb2/pvrusb2-i2c-core.c @@ -488,7 +488,7 @@ static int pvr2_i2c_xfer(struct i2c_adapter *i2c_adap, if ((ret > 0) || !(msgs[idx].flags & I2C_M_RD)) { if (cnt > 8) cnt = 8; printk(KERN_CONT " ["); - for (offs = 0; offs < (cnt>8?8:cnt); offs++) { + for (offs = 0; offs < cnt; offs++) { if (offs) printk(KERN_CONT " "); printk(KERN_CONT "%02x",msgs[idx].buf[offs]); } diff --git a/drivers/media/usb/pwc/pwc-v4l.c b/drivers/media/usb/pwc/pwc-v4l.c index 92f04db6bbae..043b2b97cee6 100644 --- a/drivers/media/usb/pwc/pwc-v4l.c +++ b/drivers/media/usb/pwc/pwc-v4l.c @@ -568,7 +568,8 @@ static int pwc_g_volatile_ctrl(struct v4l2_ctrl *ctrl) pdev->gain_valid = true; if (!DEVICE_USE_CODEC3(pdev->type)) break; - /* Fall through for CODEC3 where autogain also controls expo */ + /* For CODEC3 where autogain also controls expo */ + /* fall through */ case V4L2_CID_EXPOSURE_AUTO: if (pdev->exposure_valid && time_before(jiffies, pdev->last_exposure_update + HZ / 4)) { diff --git a/drivers/media/usb/rainshadow-cec/rainshadow-cec.c b/drivers/media/usb/rainshadow-cec/rainshadow-cec.c index 4126552c9055..f203699e9c1b 100644 --- a/drivers/media/usb/rainshadow-cec/rainshadow-cec.c +++ b/drivers/media/usb/rainshadow-cec/rainshadow-cec.c @@ -98,16 +98,13 @@ static void rain_process_msg(struct rain *rain) switch (stat) { case 1: - cec_transmit_done(rain->adap, CEC_TX_STATUS_OK, - 0, 0, 0, 0); + cec_transmit_attempt_done(rain->adap, CEC_TX_STATUS_OK); break; case 2: - cec_transmit_done(rain->adap, CEC_TX_STATUS_NACK, - 0, 1, 0, 0); + cec_transmit_attempt_done(rain->adap, CEC_TX_STATUS_NACK); break; default: - cec_transmit_done(rain->adap, CEC_TX_STATUS_LOW_DRIVE, - 0, 0, 0, 1); + cec_transmit_attempt_done(rain->adap, CEC_TX_STATUS_LOW_DRIVE); break; } } @@ -123,11 +120,12 @@ static void rain_irq_work_handler(struct work_struct *work) char data; spin_lock_irqsave(&rain->buf_lock, flags); - exit_loop = rain->buf_len == 0; if (rain->buf_len) { data = rain->buf[rain->buf_rd_idx]; rain->buf_len--; rain->buf_rd_idx = (rain->buf_rd_idx + 1) & 0xff; + } else { + exit_loop = true; } spin_unlock_irqrestore(&rain->buf_lock, flags); @@ -296,7 +294,7 @@ static int rain_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, cec_msg_destination(msg), msg->msg[1]); for (i = 2; i < msg->len; i++) { snprintf(hex, sizeof(hex), "%02x", msg->msg[i]); - strncat(cmd, hex, sizeof(cmd)); + strlcat(cmd, hex, sizeof(cmd)); } } mutex_lock(&rain->write_lock); diff --git a/drivers/media/usb/s2255/s2255drv.c b/drivers/media/usb/s2255/s2255drv.c index a9d4484f7626..6a88b1dbb3a0 100644 --- a/drivers/media/usb/s2255/s2255drv.c +++ b/drivers/media/usb/s2255/s2255drv.c @@ -1803,6 +1803,8 @@ static int save_frame(struct s2255_dev *dev, struct s2255_pipeinfo *pipe_info) default: pr_info("s2255 unknown resp\n"); } + pdata++; + break; default: pdata++; break; diff --git a/drivers/media/usb/tm6000/tm6000-input.c b/drivers/media/usb/tm6000/tm6000-input.c index 39c15bb2b20c..1a033f57fcc1 100644 --- a/drivers/media/usb/tm6000/tm6000-input.c +++ b/drivers/media/usb/tm6000/tm6000-input.c @@ -63,7 +63,6 @@ struct tm6000_IR { u8 wait:1; u8 pwled:2; u8 submit_urb:1; - u16 key_addr; struct urb *int_urb; /* IR device properties */ @@ -321,9 +320,6 @@ static int tm6000_ir_change_protocol(struct rc_dev *rc, u64 *rc_type) dprintk(2, "%s\n",__func__); - if ((rc->rc_map.scan) && (*rc_type == RC_BIT_NEC)) - ir->key_addr = ((rc->rc_map.scan[0].scancode >> 8) & 0xffff); - ir->rc_type = *rc_type; tm6000_ir_config(ir); diff --git a/drivers/media/usb/usbvision/usbvision-i2c.c b/drivers/media/usb/usbvision/usbvision-i2c.c index 5a3f788ad033..fdf6b6e285da 100644 --- a/drivers/media/usb/usbvision/usbvision-i2c.c +++ b/drivers/media/usb/usbvision/usbvision-i2c.c @@ -311,10 +311,13 @@ usbvision_i2c_read_max4(struct usb_usbvision *usbvision, unsigned char addr, switch (len) { case 4: buf[3] = usbvision_read_reg(usbvision, USBVISION_SER_DAT4); + /* fall through */ case 3: buf[2] = usbvision_read_reg(usbvision, USBVISION_SER_DAT3); + /* fall through */ case 2: buf[1] = usbvision_read_reg(usbvision, USBVISION_SER_DAT2); + /* fall through */ case 1: buf[0] = usbvision_read_reg(usbvision, USBVISION_SER_DAT1); break; diff --git a/drivers/media/usb/usbvision/usbvision-video.c b/drivers/media/usb/usbvision/usbvision-video.c index f9c3325aa4d4..756322c4ac05 100644 --- a/drivers/media/usb/usbvision/usbvision-video.c +++ b/drivers/media/usb/usbvision/usbvision-video.c @@ -1427,8 +1427,8 @@ static int usbvision_probe(struct usb_interface *intf, int model, i, ret; PDEBUG(DBG_PROBE, "VID=%#04x, PID=%#04x, ifnum=%u", - dev->descriptor.idVendor, - dev->descriptor.idProduct, ifnum); + le16_to_cpu(dev->descriptor.idVendor), + le16_to_cpu(dev->descriptor.idProduct), ifnum); model = devid->driver_info; if (model < 0 || model >= usbvision_device_data_size) { diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c index 46d6be0bb316..70842c5af05b 100644 --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -2013,6 +2013,7 @@ static int uvc_probe(struct usb_interface *intf, { struct usb_device *udev = interface_to_usbdev(intf); struct uvc_device *dev; + int function; int ret; if (id->idVendor && id->idProduct) @@ -2044,9 +2045,27 @@ static int uvc_probe(struct usb_interface *intf, strlcpy(dev->name, udev->product, sizeof dev->name); else snprintf(dev->name, sizeof dev->name, - "UVC Camera (%04x:%04x)", - le16_to_cpu(udev->descriptor.idVendor), - le16_to_cpu(udev->descriptor.idProduct)); + "UVC Camera (%04x:%04x)", + le16_to_cpu(udev->descriptor.idVendor), + le16_to_cpu(udev->descriptor.idProduct)); + + /* + * Add iFunction or iInterface to names when available as additional + * distinguishers between interfaces. iFunction is prioritized over + * iInterface which matches Windows behavior at the point of writing. + */ + if (intf->intf_assoc && intf->intf_assoc->iFunction != 0) + function = intf->intf_assoc->iFunction; + else + function = intf->cur_altsetting->desc.iInterface; + if (function != 0) { + size_t len; + + strlcat(dev->name, ": ", sizeof(dev->name)); + len = strlen(dev->name); + usb_string(udev, function, dev->name + len, + sizeof(dev->name) - len); + } /* Parse the Video Class control descriptor. */ if (uvc_parse_control(dev) < 0) { @@ -2441,6 +2460,15 @@ static struct usb_device_id uvc_ids[] = { .bInterfaceProtocol = 0, .driver_info = UVC_QUIRK_PROBE_MINMAX | UVC_QUIRK_BUILTIN_ISIGHT }, + /* Apple Built-In iSight via iBridge */ + { .match_flags = USB_DEVICE_ID_MATCH_DEVICE + | USB_DEVICE_ID_MATCH_INT_INFO, + .idVendor = 0x05ac, + .idProduct = 0x8600, + .bInterfaceClass = USB_CLASS_VIDEO, + .bInterfaceSubClass = 1, + .bInterfaceProtocol = 0, + .driver_info = UVC_QUIRK_PROBE_DEF }, /* Foxlink ("HP Webcam" on HP Mini 5103) */ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_INFO, diff --git a/drivers/media/usb/uvc/uvc_video.c b/drivers/media/usb/uvc/uvc_video.c index 47d93a938dde..fb86d6af398d 100644 --- a/drivers/media/usb/uvc/uvc_video.c +++ b/drivers/media/usb/uvc/uvc_video.c @@ -1323,11 +1323,11 @@ static void uvc_video_complete(struct urb *urb) default: uvc_printk(KERN_WARNING, "Non-zero status (%d) in video " "completion handler.\n", urb->status); - + /* fall through */ case -ENOENT: /* usb_kill_urb() called. */ if (stream->frozen) return; - + /* fall through */ case -ECONNRESET: /* usb_unlink_urb() called. */ case -ESHUTDOWN: /* The endpoint is being disabled. */ uvc_queue_cancel(queue, urb->status == -ESHUTDOWN); diff --git a/drivers/media/v4l2-core/Kconfig b/drivers/media/v4l2-core/Kconfig index 6b1b78ff1417..a35c33686abf 100644 --- a/drivers/media/v4l2-core/Kconfig +++ b/drivers/media/v4l2-core/Kconfig @@ -55,6 +55,9 @@ config V4L2_FLASH_LED_CLASS When in doubt, say N. +config V4L2_FWNODE + tristate + # Used by drivers that need Videobuf modules config VIDEOBUF_GEN tristate diff --git a/drivers/media/v4l2-core/Makefile b/drivers/media/v4l2-core/Makefile index 795a5352761d..098ad5fd5231 100644 --- a/drivers/media/v4l2-core/Makefile +++ b/drivers/media/v4l2-core/Makefile @@ -10,9 +10,7 @@ videodev-objs := v4l2-dev.o v4l2-ioctl.o v4l2-device.o v4l2-fh.o \ ifeq ($(CONFIG_COMPAT),y) videodev-objs += v4l2-compat-ioctl32.o endif -ifeq ($(CONFIG_OF),y) - videodev-objs += v4l2-of.o -endif +obj-$(CONFIG_V4L2_FWNODE) += v4l2-fwnode.o ifeq ($(CONFIG_TRACEPOINTS),y) videodev-objs += vb2-trace.o v4l2-trace.o endif diff --git a/drivers/media/v4l2-core/v4l2-async.c b/drivers/media/v4l2-core/v4l2-async.c index 96cc733f35ef..cbd919d4edd2 100644 --- a/drivers/media/v4l2-core/v4l2-async.c +++ b/drivers/media/v4l2-core/v4l2-async.c @@ -14,6 +14,7 @@ #include <linux/list.h> #include <linux/module.h> #include <linux/mutex.h> +#include <linux/of.h> #include <linux/platform_device.h> #include <linux/slab.h> #include <linux/types.h> @@ -40,10 +41,14 @@ static bool match_devname(struct v4l2_subdev *sd, return !strcmp(asd->match.device_name.name, dev_name(sd->dev)); } -static bool match_of(struct v4l2_subdev *sd, struct v4l2_async_subdev *asd) +static bool match_fwnode(struct v4l2_subdev *sd, struct v4l2_async_subdev *asd) { - return !of_node_cmp(of_node_full_name(sd->of_node), - of_node_full_name(asd->match.of.node)); + if (!is_of_node(sd->fwnode) || !is_of_node(asd->match.fwnode.fwnode)) + return sd->fwnode == asd->match.fwnode.fwnode; + + return !of_node_cmp(of_node_full_name(to_of_node(sd->fwnode)), + of_node_full_name( + to_of_node(asd->match.fwnode.fwnode))); } static bool match_custom(struct v4l2_subdev *sd, struct v4l2_async_subdev *asd) @@ -77,8 +82,8 @@ static struct v4l2_async_subdev *v4l2_async_belongs(struct v4l2_async_notifier * case V4L2_ASYNC_MATCH_I2C: match = match_i2c; break; - case V4L2_ASYNC_MATCH_OF: - match = match_of; + case V4L2_ASYNC_MATCH_FWNODE: + match = match_fwnode; break; default: /* Cannot happen, unless someone breaks us */ @@ -157,7 +162,7 @@ int v4l2_async_notifier_register(struct v4l2_device *v4l2_dev, case V4L2_ASYNC_MATCH_CUSTOM: case V4L2_ASYNC_MATCH_DEVNAME: case V4L2_ASYNC_MATCH_I2C: - case V4L2_ASYNC_MATCH_OF: + case V4L2_ASYNC_MATCH_FWNODE: break; default: dev_err(notifier->v4l2_dev ? notifier->v4l2_dev->dev : NULL, @@ -280,8 +285,8 @@ int v4l2_async_register_subdev(struct v4l2_subdev *sd) * (struct v4l2_subdev.dev), and async sub-device does not * exist independently of the device at any point of time. */ - if (!sd->of_node && sd->dev) - sd->of_node = sd->dev->of_node; + if (!sd->fwnode && sd->dev) + sd->fwnode = dev_fwnode(sd->dev); mutex_lock(&list_lock); diff --git a/drivers/media/v4l2-core/v4l2-ctrls.c b/drivers/media/v4l2-core/v4l2-ctrls.c index ec42872d11cf..36eede3ff098 100644 --- a/drivers/media/v4l2-core/v4l2-ctrls.c +++ b/drivers/media/v4l2-core/v4l2-ctrls.c @@ -886,6 +886,7 @@ const char *v4l2_ctrl_get_name(u32 id) case V4L2_CID_PIXEL_RATE: return "Pixel Rate"; case V4L2_CID_TEST_PATTERN: return "Test Pattern"; case V4L2_CID_DEINTERLACING_MODE: return "Deinterlacing Mode"; + case V4L2_CID_DIGITAL_GAIN: return "Digital Gain"; /* DV controls */ /* Keep the order of the 'case's the same as in v4l2-controls.h! */ @@ -1739,8 +1740,8 @@ int v4l2_ctrl_handler_init_class(struct v4l2_ctrl_handler *hdl, unsigned nr_of_controls_hint, struct lock_class_key *key, const char *name) { + mutex_init(&hdl->_lock); hdl->lock = &hdl->_lock; - mutex_init(hdl->lock); lockdep_set_class_and_name(hdl->lock, key, name); INIT_LIST_HEAD(&hdl->ctrls); INIT_LIST_HEAD(&hdl->ctrl_refs); @@ -1780,6 +1781,7 @@ void v4l2_ctrl_handler_free(struct v4l2_ctrl_handler *hdl) hdl->cached = NULL; hdl->error = 0; mutex_unlock(hdl->lock); + mutex_destroy(&hdl->_lock); } EXPORT_SYMBOL(v4l2_ctrl_handler_free); @@ -2444,14 +2446,16 @@ int v4l2_ctrl_subdev_log_status(struct v4l2_subdev *sd) EXPORT_SYMBOL(v4l2_ctrl_subdev_log_status); /* Call s_ctrl for all controls owned by the handler */ -int v4l2_ctrl_handler_setup(struct v4l2_ctrl_handler *hdl) +int __v4l2_ctrl_handler_setup(struct v4l2_ctrl_handler *hdl) { struct v4l2_ctrl *ctrl; int ret = 0; if (hdl == NULL) return 0; - mutex_lock(hdl->lock); + + lockdep_assert_held(hdl->lock); + list_for_each_entry(ctrl, &hdl->ctrls, node) ctrl->done = false; @@ -2476,7 +2480,22 @@ int v4l2_ctrl_handler_setup(struct v4l2_ctrl_handler *hdl) if (ret) break; } + + return ret; +} +EXPORT_SYMBOL_GPL(__v4l2_ctrl_handler_setup); + +int v4l2_ctrl_handler_setup(struct v4l2_ctrl_handler *hdl) +{ + int ret; + + if (hdl == NULL) + return 0; + + mutex_lock(hdl->lock); + ret = __v4l2_ctrl_handler_setup(hdl); mutex_unlock(hdl->lock); + return ret; } EXPORT_SYMBOL(v4l2_ctrl_handler_setup); diff --git a/drivers/media/v4l2-core/v4l2-flash-led-class.c b/drivers/media/v4l2-core/v4l2-flash-led-class.c index 794e563f24f8..7b8288108e8a 100644 --- a/drivers/media/v4l2-core/v4l2-flash-led-class.c +++ b/drivers/media/v4l2-core/v4l2-flash-led-class.c @@ -12,7 +12,7 @@ #include <linux/led-class-flash.h> #include <linux/module.h> #include <linux/mutex.h> -#include <linux/of.h> +#include <linux/property.h> #include <linux/slab.h> #include <linux/types.h> #include <media/v4l2-flash-led-class.h> @@ -612,7 +612,7 @@ static const struct v4l2_subdev_internal_ops v4l2_flash_subdev_internal_ops = { static const struct v4l2_subdev_ops v4l2_flash_subdev_ops; struct v4l2_flash *v4l2_flash_init( - struct device *dev, struct device_node *of_node, + struct device *dev, struct fwnode_handle *fwn, struct led_classdev_flash *fled_cdev, struct led_classdev_flash *iled_cdev, const struct v4l2_flash_ops *ops, @@ -638,7 +638,7 @@ struct v4l2_flash *v4l2_flash_init( v4l2_flash->iled_cdev = iled_cdev; v4l2_flash->ops = ops; sd->dev = dev; - sd->of_node = of_node ? of_node : led_cdev->dev->of_node; + sd->fwnode = fwn ? fwn : dev_fwnode(led_cdev->dev); v4l2_subdev_init(sd, &v4l2_flash_subdev_ops); sd->internal_ops = &v4l2_flash_subdev_internal_ops; sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; @@ -654,7 +654,7 @@ struct v4l2_flash *v4l2_flash_init( if (ret < 0) goto err_init_controls; - of_node_get(sd->of_node); + fwnode_handle_get(sd->fwnode); ret = v4l2_async_register_subdev(sd); if (ret < 0) @@ -663,7 +663,7 @@ struct v4l2_flash *v4l2_flash_init( return v4l2_flash; err_async_register_sd: - of_node_put(sd->of_node); + fwnode_handle_put(sd->fwnode); v4l2_ctrl_handler_free(sd->ctrl_handler); err_init_controls: media_entity_cleanup(&sd->entity); @@ -683,7 +683,7 @@ void v4l2_flash_release(struct v4l2_flash *v4l2_flash) v4l2_async_unregister_subdev(sd); - of_node_put(sd->of_node); + fwnode_handle_put(sd->fwnode); v4l2_ctrl_handler_free(sd->ctrl_handler); media_entity_cleanup(&sd->entity); diff --git a/drivers/media/v4l2-core/v4l2-fwnode.c b/drivers/media/v4l2-core/v4l2-fwnode.c new file mode 100644 index 000000000000..153c53ca3925 --- /dev/null +++ b/drivers/media/v4l2-core/v4l2-fwnode.c @@ -0,0 +1,345 @@ +/* + * V4L2 fwnode binding parsing library + * + * The origins of the V4L2 fwnode library are in V4L2 OF library that + * formerly was located in v4l2-of.c. + * + * Copyright (c) 2016 Intel Corporation. + * Author: Sakari Ailus <sakari.ailus@linux.intel.com> + * + * Copyright (C) 2012 - 2013 Samsung Electronics Co., Ltd. + * Author: Sylwester Nawrocki <s.nawrocki@samsung.com> + * + * Copyright (C) 2012 Renesas Electronics Corp. + * Author: Guennadi Liakhovetski <g.liakhovetski@gmx.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + */ +#include <linux/acpi.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/property.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/types.h> + +#include <media/v4l2-fwnode.h> + +static int v4l2_fwnode_endpoint_parse_csi_bus(struct fwnode_handle *fwnode, + struct v4l2_fwnode_endpoint *vep) +{ + struct v4l2_fwnode_bus_mipi_csi2 *bus = &vep->bus.mipi_csi2; + bool have_clk_lane = false; + unsigned int flags = 0, lanes_used = 0; + unsigned int i; + u32 v; + int rval; + + rval = fwnode_property_read_u32_array(fwnode, "data-lanes", NULL, 0); + if (rval > 0) { + u32 array[ARRAY_SIZE(bus->data_lanes)]; + + bus->num_data_lanes = + min_t(int, ARRAY_SIZE(bus->data_lanes), rval); + + fwnode_property_read_u32_array(fwnode, "data-lanes", array, + bus->num_data_lanes); + + for (i = 0; i < bus->num_data_lanes; i++) { + if (lanes_used & BIT(array[i])) + pr_warn("duplicated lane %u in data-lanes\n", + array[i]); + lanes_used |= BIT(array[i]); + + bus->data_lanes[i] = array[i]; + } + } + + rval = fwnode_property_read_u32_array(fwnode, "lane-polarities", NULL, + 0); + if (rval > 0) { + u32 array[ARRAY_SIZE(bus->lane_polarities)]; + + if (rval < 1 + bus->num_data_lanes /* clock + data */) { + pr_warn("too few lane-polarities entries (need %u, got %u)\n", + 1 + bus->num_data_lanes, rval); + return -EINVAL; + } + + fwnode_property_read_u32_array(fwnode, "lane-polarities", array, + 1 + bus->num_data_lanes); + + for (i = 0; i < 1 + bus->num_data_lanes; i++) + bus->lane_polarities[i] = array[i]; + } + + if (!fwnode_property_read_u32(fwnode, "clock-lanes", &v)) { + if (lanes_used & BIT(v)) + pr_warn("duplicated lane %u in clock-lanes\n", v); + lanes_used |= BIT(v); + + bus->clock_lane = v; + have_clk_lane = true; + } + + if (fwnode_property_present(fwnode, "clock-noncontinuous")) + flags |= V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK; + else if (have_clk_lane || bus->num_data_lanes > 0) + flags |= V4L2_MBUS_CSI2_CONTINUOUS_CLOCK; + + bus->flags = flags; + vep->bus_type = V4L2_MBUS_CSI2; + + return 0; +} + +static void v4l2_fwnode_endpoint_parse_parallel_bus( + struct fwnode_handle *fwnode, struct v4l2_fwnode_endpoint *vep) +{ + struct v4l2_fwnode_bus_parallel *bus = &vep->bus.parallel; + unsigned int flags = 0; + u32 v; + + if (!fwnode_property_read_u32(fwnode, "hsync-active", &v)) + flags |= v ? V4L2_MBUS_HSYNC_ACTIVE_HIGH : + V4L2_MBUS_HSYNC_ACTIVE_LOW; + + if (!fwnode_property_read_u32(fwnode, "vsync-active", &v)) + flags |= v ? V4L2_MBUS_VSYNC_ACTIVE_HIGH : + V4L2_MBUS_VSYNC_ACTIVE_LOW; + + if (!fwnode_property_read_u32(fwnode, "field-even-active", &v)) + flags |= v ? V4L2_MBUS_FIELD_EVEN_HIGH : + V4L2_MBUS_FIELD_EVEN_LOW; + if (flags) + vep->bus_type = V4L2_MBUS_PARALLEL; + else + vep->bus_type = V4L2_MBUS_BT656; + + if (!fwnode_property_read_u32(fwnode, "pclk-sample", &v)) + flags |= v ? V4L2_MBUS_PCLK_SAMPLE_RISING : + V4L2_MBUS_PCLK_SAMPLE_FALLING; + + if (!fwnode_property_read_u32(fwnode, "data-active", &v)) + flags |= v ? V4L2_MBUS_DATA_ACTIVE_HIGH : + V4L2_MBUS_DATA_ACTIVE_LOW; + + if (fwnode_property_present(fwnode, "slave-mode")) + flags |= V4L2_MBUS_SLAVE; + else + flags |= V4L2_MBUS_MASTER; + + if (!fwnode_property_read_u32(fwnode, "bus-width", &v)) + bus->bus_width = v; + + if (!fwnode_property_read_u32(fwnode, "data-shift", &v)) + bus->data_shift = v; + + if (!fwnode_property_read_u32(fwnode, "sync-on-green-active", &v)) + flags |= v ? V4L2_MBUS_VIDEO_SOG_ACTIVE_HIGH : + V4L2_MBUS_VIDEO_SOG_ACTIVE_LOW; + + bus->flags = flags; + +} + +/** + * v4l2_fwnode_endpoint_parse() - parse all fwnode node properties + * @fwnode: pointer to the endpoint's fwnode handle + * @vep: pointer to the V4L2 fwnode data structure + * + * All properties are optional. If none are found, we don't set any flags. This + * means the port has a static configuration and no properties have to be + * specified explicitly. If any properties that identify the bus as parallel + * are found and slave-mode isn't set, we set V4L2_MBUS_MASTER. Similarly, if + * we recognise the bus as serial CSI-2 and clock-noncontinuous isn't set, we + * set the V4L2_MBUS_CSI2_CONTINUOUS_CLOCK flag. The caller should hold a + * reference to @fwnode. + * + * NOTE: This function does not parse properties the size of which is variable + * without a low fixed limit. Please use v4l2_fwnode_endpoint_alloc_parse() in + * new drivers instead. + * + * Return: 0 on success or a negative error code on failure. + */ +int v4l2_fwnode_endpoint_parse(struct fwnode_handle *fwnode, + struct v4l2_fwnode_endpoint *vep) +{ + int rval; + + fwnode_graph_parse_endpoint(fwnode, &vep->base); + + /* Zero fields from bus_type to until the end */ + memset(&vep->bus_type, 0, sizeof(*vep) - + offsetof(typeof(*vep), bus_type)); + + rval = v4l2_fwnode_endpoint_parse_csi_bus(fwnode, vep); + if (rval) + return rval; + /* + * Parse the parallel video bus properties only if none + * of the MIPI CSI-2 specific properties were found. + */ + if (vep->bus.mipi_csi2.flags == 0) + v4l2_fwnode_endpoint_parse_parallel_bus(fwnode, vep); + + return 0; +} +EXPORT_SYMBOL_GPL(v4l2_fwnode_endpoint_parse); + +/* + * v4l2_fwnode_endpoint_free() - free the V4L2 fwnode acquired by + * v4l2_fwnode_endpoint_alloc_parse() + * @vep - the V4L2 fwnode the resources of which are to be released + * + * It is safe to call this function with NULL argument or on a V4L2 fwnode the + * parsing of which failed. + */ +void v4l2_fwnode_endpoint_free(struct v4l2_fwnode_endpoint *vep) +{ + if (IS_ERR_OR_NULL(vep)) + return; + + kfree(vep->link_frequencies); + kfree(vep); +} +EXPORT_SYMBOL_GPL(v4l2_fwnode_endpoint_free); + +/** + * v4l2_fwnode_endpoint_alloc_parse() - parse all fwnode node properties + * @fwnode: pointer to the endpoint's fwnode handle + * + * All properties are optional. If none are found, we don't set any flags. This + * means the port has a static configuration and no properties have to be + * specified explicitly. If any properties that identify the bus as parallel + * are found and slave-mode isn't set, we set V4L2_MBUS_MASTER. Similarly, if + * we recognise the bus as serial CSI-2 and clock-noncontinuous isn't set, we + * set the V4L2_MBUS_CSI2_CONTINUOUS_CLOCK flag. The caller should hold a + * reference to @fwnode. + * + * v4l2_fwnode_endpoint_alloc_parse() has two important differences to + * v4l2_fwnode_endpoint_parse(): + * + * 1. It also parses variable size data. + * + * 2. The memory it has allocated to store the variable size data must be freed + * using v4l2_fwnode_endpoint_free() when no longer needed. + * + * Return: Pointer to v4l2_fwnode_endpoint if successful, on an error pointer + * on error. + */ +struct v4l2_fwnode_endpoint *v4l2_fwnode_endpoint_alloc_parse( + struct fwnode_handle *fwnode) +{ + struct v4l2_fwnode_endpoint *vep; + int rval; + + vep = kzalloc(sizeof(*vep), GFP_KERNEL); + if (!vep) + return ERR_PTR(-ENOMEM); + + rval = v4l2_fwnode_endpoint_parse(fwnode, vep); + if (rval < 0) + goto out_err; + + rval = fwnode_property_read_u64_array(fwnode, "link-frequencies", + NULL, 0); + if (rval < 0) + goto out_err; + + vep->link_frequencies = + kmalloc_array(rval, sizeof(*vep->link_frequencies), GFP_KERNEL); + if (!vep->link_frequencies) { + rval = -ENOMEM; + goto out_err; + } + + vep->nr_of_link_frequencies = rval; + + rval = fwnode_property_read_u64_array(fwnode, "link-frequencies", + vep->link_frequencies, + vep->nr_of_link_frequencies); + if (rval < 0) + goto out_err; + + return vep; + +out_err: + v4l2_fwnode_endpoint_free(vep); + return ERR_PTR(rval); +} +EXPORT_SYMBOL_GPL(v4l2_fwnode_endpoint_alloc_parse); + +/** + * v4l2_fwnode_endpoint_parse_link() - parse a link between two endpoints + * @__fwnode: pointer to the endpoint's fwnode at the local end of the link + * @link: pointer to the V4L2 fwnode link data structure + * + * Fill the link structure with the local and remote nodes and port numbers. + * The local_node and remote_node fields are set to point to the local and + * remote port's parent nodes respectively (the port parent node being the + * parent node of the port node if that node isn't a 'ports' node, or the + * grand-parent node of the port node otherwise). + * + * A reference is taken to both the local and remote nodes, the caller must use + * v4l2_fwnode_endpoint_put_link() to drop the references when done with the + * link. + * + * Return: 0 on success, or -ENOLINK if the remote endpoint fwnode can't be + * found. + */ +int v4l2_fwnode_parse_link(struct fwnode_handle *__fwnode, + struct v4l2_fwnode_link *link) +{ + const char *port_prop = is_of_node(__fwnode) ? "reg" : "port"; + struct fwnode_handle *fwnode; + + memset(link, 0, sizeof(*link)); + + fwnode = fwnode_get_parent(__fwnode); + fwnode_property_read_u32(fwnode, port_prop, &link->local_port); + fwnode = fwnode_get_next_parent(fwnode); + if (is_of_node(fwnode) && + of_node_cmp(to_of_node(fwnode)->name, "ports") == 0) + fwnode = fwnode_get_next_parent(fwnode); + link->local_node = fwnode; + + fwnode = fwnode_graph_get_remote_endpoint(__fwnode); + if (!fwnode) { + fwnode_handle_put(fwnode); + return -ENOLINK; + } + + fwnode = fwnode_get_parent(fwnode); + fwnode_property_read_u32(fwnode, port_prop, &link->remote_port); + fwnode = fwnode_get_next_parent(fwnode); + if (is_of_node(fwnode) && + of_node_cmp(to_of_node(fwnode)->name, "ports") == 0) + fwnode = fwnode_get_next_parent(fwnode); + link->remote_node = fwnode; + + return 0; +} +EXPORT_SYMBOL_GPL(v4l2_fwnode_parse_link); + +/** + * v4l2_fwnode_put_link() - drop references to nodes in a link + * @link: pointer to the V4L2 fwnode link data structure + * + * Drop references to the local and remote nodes in the link. This function + * must be called on every link parsed with v4l2_fwnode_parse_link(). + */ +void v4l2_fwnode_put_link(struct v4l2_fwnode_link *link) +{ + fwnode_handle_put(link->local_node); + fwnode_handle_put(link->remote_node); +} +EXPORT_SYMBOL_GPL(v4l2_fwnode_put_link); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Sakari Ailus <sakari.ailus@linux.intel.com>"); +MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>"); +MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>"); diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c index e5a2187381db..ce40183d9daa 100644 --- a/drivers/media/v4l2-core/v4l2-ioctl.c +++ b/drivers/media/v4l2-core/v4l2-ioctl.c @@ -1229,6 +1229,9 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt) case V4L2_SDR_FMT_CS8: descr = "Complex S8"; break; case V4L2_SDR_FMT_CS14LE: descr = "Complex S14LE"; break; case V4L2_SDR_FMT_RU12LE: descr = "Real U12LE"; break; + case V4L2_SDR_FMT_PCU16BE: descr = "Planar Complex U16BE"; break; + case V4L2_SDR_FMT_PCU18BE: descr = "Planar Complex U18BE"; break; + case V4L2_SDR_FMT_PCU20BE: descr = "Planar Complex U20BE"; break; case V4L2_TCH_FMT_DELTA_TD16: descr = "16-bit signed deltas"; break; case V4L2_TCH_FMT_DELTA_TD08: descr = "8-bit signed deltas"; break; case V4L2_TCH_FMT_TU16: descr = "16-bit unsigned touch data"; break; @@ -2472,20 +2475,22 @@ struct v4l2_ioctl_info { }; /* This control needs a priority check */ -#define INFO_FL_PRIO (1 << 0) +#define INFO_FL_PRIO (1 << 0) /* This control can be valid if the filehandle passes a control handler. */ -#define INFO_FL_CTRL (1 << 1) +#define INFO_FL_CTRL (1 << 1) /* This is a standard ioctl, no need for special code */ -#define INFO_FL_STD (1 << 2) +#define INFO_FL_STD (1 << 2) /* This is ioctl has its own function */ -#define INFO_FL_FUNC (1 << 3) +#define INFO_FL_FUNC (1 << 3) /* Queuing ioctl */ -#define INFO_FL_QUEUE (1 << 4) +#define INFO_FL_QUEUE (1 << 4) +/* Always copy back result, even on error */ +#define INFO_FL_ALWAYS_COPY (1 << 5) /* Zero struct from after the field to the end */ #define INFO_FL_CLEAR(v4l2_struct, field) \ ((offsetof(struct v4l2_struct, field) + \ sizeof(((struct v4l2_struct *)0)->field)) << 16) -#define INFO_FL_CLEAR_MASK (_IOC_SIZEMASK << 16) +#define INFO_FL_CLEAR_MASK (_IOC_SIZEMASK << 16) #define IOCTL_INFO_STD(_ioctl, _vidioc, _debug, _flags) \ [_IOC_NR(_ioctl)] = { \ @@ -2536,8 +2541,8 @@ static struct v4l2_ioctl_info v4l2_ioctls[] = { IOCTL_INFO_FNC(VIDIOC_QUERYMENU, v4l_querymenu, v4l_print_querymenu, INFO_FL_CTRL | INFO_FL_CLEAR(v4l2_querymenu, index)), IOCTL_INFO_STD(VIDIOC_G_INPUT, vidioc_g_input, v4l_print_u32, 0), IOCTL_INFO_FNC(VIDIOC_S_INPUT, v4l_s_input, v4l_print_u32, INFO_FL_PRIO), - IOCTL_INFO_STD(VIDIOC_G_EDID, vidioc_g_edid, v4l_print_edid, 0), - IOCTL_INFO_STD(VIDIOC_S_EDID, vidioc_s_edid, v4l_print_edid, INFO_FL_PRIO), + IOCTL_INFO_STD(VIDIOC_G_EDID, vidioc_g_edid, v4l_print_edid, INFO_FL_ALWAYS_COPY), + IOCTL_INFO_STD(VIDIOC_S_EDID, vidioc_s_edid, v4l_print_edid, INFO_FL_PRIO | INFO_FL_ALWAYS_COPY), IOCTL_INFO_STD(VIDIOC_G_OUTPUT, vidioc_g_output, v4l_print_u32, 0), IOCTL_INFO_FNC(VIDIOC_S_OUTPUT, v4l_s_output, v4l_print_u32, INFO_FL_PRIO), IOCTL_INFO_FNC(VIDIOC_ENUMOUTPUT, v4l_enumoutput, v4l_print_enumoutput, INFO_FL_CLEAR(v4l2_output, index)), @@ -2583,7 +2588,7 @@ static struct v4l2_ioctl_info v4l2_ioctls[] = { IOCTL_INFO_FNC(VIDIOC_CREATE_BUFS, v4l_create_bufs, v4l_print_create_buffers, INFO_FL_PRIO | INFO_FL_QUEUE), IOCTL_INFO_FNC(VIDIOC_PREPARE_BUF, v4l_prepare_buf, v4l_print_buffer, INFO_FL_QUEUE), IOCTL_INFO_STD(VIDIOC_ENUM_DV_TIMINGS, vidioc_enum_dv_timings, v4l_print_enum_dv_timings, INFO_FL_CLEAR(v4l2_enum_dv_timings, pad)), - IOCTL_INFO_STD(VIDIOC_QUERY_DV_TIMINGS, vidioc_query_dv_timings, v4l_print_dv_timings, 0), + IOCTL_INFO_STD(VIDIOC_QUERY_DV_TIMINGS, vidioc_query_dv_timings, v4l_print_dv_timings, INFO_FL_ALWAYS_COPY), IOCTL_INFO_STD(VIDIOC_DV_TIMINGS_CAP, vidioc_dv_timings_cap, v4l_print_dv_timings_cap, INFO_FL_CLEAR(v4l2_dv_timings_cap, type)), IOCTL_INFO_FNC(VIDIOC_ENUM_FREQ_BANDS, v4l_enum_freq_bands, v4l_print_freq_band, 0), IOCTL_INFO_FNC(VIDIOC_DBG_G_CHIP_INFO, v4l_dbg_g_chip_info, v4l_print_dbg_chip_info, INFO_FL_CLEAR(v4l2_dbg_chip_info, match)), @@ -2801,6 +2806,7 @@ video_usercopy(struct file *file, unsigned int cmd, unsigned long arg, void *parg = (void *)arg; long err = -EINVAL; bool has_array_args; + bool always_copy = false; size_t array_size = 0; void __user *user_ptr = NULL; void **kernel_ptr = NULL; @@ -2830,8 +2836,10 @@ video_usercopy(struct file *file, unsigned int cmd, unsigned long arg, */ if (v4l2_is_known_ioctl(cmd)) { u32 flags = v4l2_ioctls[_IOC_NR(cmd)].flags; + if (flags & INFO_FL_CLEAR_MASK) n = (flags & INFO_FL_CLEAR_MASK) >> 16; + always_copy = flags & INFO_FL_ALWAYS_COPY; } if (copy_from_user(parg, (void __user *)arg, n)) @@ -2885,9 +2893,11 @@ video_usercopy(struct file *file, unsigned int cmd, unsigned long arg, err = -EFAULT; goto out_array_args; } - /* VIDIOC_QUERY_DV_TIMINGS can return an error, but still have valid - results that must be returned. */ - if (err < 0 && cmd != VIDIOC_QUERY_DV_TIMINGS) + /* + * Some ioctls can return an error, but still have valid + * results that must be returned. + */ + if (err < 0 && !always_copy) goto out; out_array_args: diff --git a/drivers/media/v4l2-core/v4l2-of.c b/drivers/media/v4l2-core/v4l2-of.c deleted file mode 100644 index 4f59f442dd0a..000000000000 --- a/drivers/media/v4l2-core/v4l2-of.c +++ /dev/null @@ -1,327 +0,0 @@ -/* - * V4L2 OF binding parsing library - * - * Copyright (C) 2012 - 2013 Samsung Electronics Co., Ltd. - * Author: Sylwester Nawrocki <s.nawrocki@samsung.com> - * - * Copyright (C) 2012 Renesas Electronics Corp. - * Author: Guennadi Liakhovetski <g.liakhovetski@gmx.de> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - */ -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/of.h> -#include <linux/slab.h> -#include <linux/string.h> -#include <linux/types.h> - -#include <media/v4l2-of.h> - -static int v4l2_of_parse_csi_bus(const struct device_node *node, - struct v4l2_of_endpoint *endpoint) -{ - struct v4l2_of_bus_mipi_csi2 *bus = &endpoint->bus.mipi_csi2; - struct property *prop; - bool have_clk_lane = false; - unsigned int flags = 0, lanes_used = 0; - u32 v; - - prop = of_find_property(node, "data-lanes", NULL); - if (prop) { - const __be32 *lane = NULL; - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(bus->data_lanes); i++) { - lane = of_prop_next_u32(prop, lane, &v); - if (!lane) - break; - - if (lanes_used & BIT(v)) - pr_warn("%s: duplicated lane %u in data-lanes\n", - node->full_name, v); - lanes_used |= BIT(v); - - bus->data_lanes[i] = v; - } - bus->num_data_lanes = i; - } - - prop = of_find_property(node, "lane-polarities", NULL); - if (prop) { - const __be32 *polarity = NULL; - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(bus->lane_polarities); i++) { - polarity = of_prop_next_u32(prop, polarity, &v); - if (!polarity) - break; - bus->lane_polarities[i] = v; - } - - if (i < 1 + bus->num_data_lanes /* clock + data */) { - pr_warn("%s: too few lane-polarities entries (need %u, got %u)\n", - node->full_name, 1 + bus->num_data_lanes, i); - return -EINVAL; - } - } - - if (!of_property_read_u32(node, "clock-lanes", &v)) { - if (lanes_used & BIT(v)) - pr_warn("%s: duplicated lane %u in clock-lanes\n", - node->full_name, v); - lanes_used |= BIT(v); - - bus->clock_lane = v; - have_clk_lane = true; - } - - if (of_get_property(node, "clock-noncontinuous", &v)) - flags |= V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK; - else if (have_clk_lane || bus->num_data_lanes > 0) - flags |= V4L2_MBUS_CSI2_CONTINUOUS_CLOCK; - - bus->flags = flags; - endpoint->bus_type = V4L2_MBUS_CSI2; - - return 0; -} - -static void v4l2_of_parse_parallel_bus(const struct device_node *node, - struct v4l2_of_endpoint *endpoint) -{ - struct v4l2_of_bus_parallel *bus = &endpoint->bus.parallel; - unsigned int flags = 0; - u32 v; - - if (!of_property_read_u32(node, "hsync-active", &v)) - flags |= v ? V4L2_MBUS_HSYNC_ACTIVE_HIGH : - V4L2_MBUS_HSYNC_ACTIVE_LOW; - - if (!of_property_read_u32(node, "vsync-active", &v)) - flags |= v ? V4L2_MBUS_VSYNC_ACTIVE_HIGH : - V4L2_MBUS_VSYNC_ACTIVE_LOW; - - if (!of_property_read_u32(node, "field-even-active", &v)) - flags |= v ? V4L2_MBUS_FIELD_EVEN_HIGH : - V4L2_MBUS_FIELD_EVEN_LOW; - if (flags) - endpoint->bus_type = V4L2_MBUS_PARALLEL; - else - endpoint->bus_type = V4L2_MBUS_BT656; - - if (!of_property_read_u32(node, "pclk-sample", &v)) - flags |= v ? V4L2_MBUS_PCLK_SAMPLE_RISING : - V4L2_MBUS_PCLK_SAMPLE_FALLING; - - if (!of_property_read_u32(node, "data-active", &v)) - flags |= v ? V4L2_MBUS_DATA_ACTIVE_HIGH : - V4L2_MBUS_DATA_ACTIVE_LOW; - - if (of_get_property(node, "slave-mode", &v)) - flags |= V4L2_MBUS_SLAVE; - else - flags |= V4L2_MBUS_MASTER; - - if (!of_property_read_u32(node, "bus-width", &v)) - bus->bus_width = v; - - if (!of_property_read_u32(node, "data-shift", &v)) - bus->data_shift = v; - - if (!of_property_read_u32(node, "sync-on-green-active", &v)) - flags |= v ? V4L2_MBUS_VIDEO_SOG_ACTIVE_HIGH : - V4L2_MBUS_VIDEO_SOG_ACTIVE_LOW; - - bus->flags = flags; - -} - -/** - * v4l2_of_parse_endpoint() - parse all endpoint node properties - * @node: pointer to endpoint device_node - * @endpoint: pointer to the V4L2 OF endpoint data structure - * - * All properties are optional. If none are found, we don't set any flags. - * This means the port has a static configuration and no properties have - * to be specified explicitly. - * If any properties that identify the bus as parallel are found and - * slave-mode isn't set, we set V4L2_MBUS_MASTER. Similarly, if we recognise - * the bus as serial CSI-2 and clock-noncontinuous isn't set, we set the - * V4L2_MBUS_CSI2_CONTINUOUS_CLOCK flag. - * The caller should hold a reference to @node. - * - * NOTE: This function does not parse properties the size of which is - * variable without a low fixed limit. Please use - * v4l2_of_alloc_parse_endpoint() in new drivers instead. - * - * Return: 0 on success or a negative error code on failure. - */ -int v4l2_of_parse_endpoint(const struct device_node *node, - struct v4l2_of_endpoint *endpoint) -{ - int rval; - - of_graph_parse_endpoint(node, &endpoint->base); - /* Zero fields from bus_type to until the end */ - memset(&endpoint->bus_type, 0, sizeof(*endpoint) - - offsetof(typeof(*endpoint), bus_type)); - - rval = v4l2_of_parse_csi_bus(node, endpoint); - if (rval) - return rval; - /* - * Parse the parallel video bus properties only if none - * of the MIPI CSI-2 specific properties were found. - */ - if (endpoint->bus.mipi_csi2.flags == 0) - v4l2_of_parse_parallel_bus(node, endpoint); - - return 0; -} -EXPORT_SYMBOL(v4l2_of_parse_endpoint); - -/* - * v4l2_of_free_endpoint() - free the endpoint acquired by - * v4l2_of_alloc_parse_endpoint() - * @endpoint - the endpoint the resources of which are to be released - * - * It is safe to call this function with NULL argument or on an - * endpoint the parsing of which failed. - */ -void v4l2_of_free_endpoint(struct v4l2_of_endpoint *endpoint) -{ - if (IS_ERR_OR_NULL(endpoint)) - return; - - kfree(endpoint->link_frequencies); - kfree(endpoint); -} -EXPORT_SYMBOL(v4l2_of_free_endpoint); - -/** - * v4l2_of_alloc_parse_endpoint() - parse all endpoint node properties - * @node: pointer to endpoint device_node - * - * All properties are optional. If none are found, we don't set any flags. - * This means the port has a static configuration and no properties have - * to be specified explicitly. - * If any properties that identify the bus as parallel are found and - * slave-mode isn't set, we set V4L2_MBUS_MASTER. Similarly, if we recognise - * the bus as serial CSI-2 and clock-noncontinuous isn't set, we set the - * V4L2_MBUS_CSI2_CONTINUOUS_CLOCK flag. - * The caller should hold a reference to @node. - * - * v4l2_of_alloc_parse_endpoint() has two important differences to - * v4l2_of_parse_endpoint(): - * - * 1. It also parses variable size data and - * - * 2. The memory it has allocated to store the variable size data must - * be freed using v4l2_of_free_endpoint() when no longer needed. - * - * Return: Pointer to v4l2_of_endpoint if successful, on error a - * negative error code. - */ -struct v4l2_of_endpoint *v4l2_of_alloc_parse_endpoint( - const struct device_node *node) -{ - struct v4l2_of_endpoint *endpoint; - int len; - int rval; - - endpoint = kzalloc(sizeof(*endpoint), GFP_KERNEL); - if (!endpoint) - return ERR_PTR(-ENOMEM); - - rval = v4l2_of_parse_endpoint(node, endpoint); - if (rval < 0) - goto out_err; - - if (of_get_property(node, "link-frequencies", &len)) { - endpoint->link_frequencies = kmalloc(len, GFP_KERNEL); - if (!endpoint->link_frequencies) { - rval = -ENOMEM; - goto out_err; - } - - endpoint->nr_of_link_frequencies = - len / sizeof(*endpoint->link_frequencies); - - rval = of_property_read_u64_array( - node, "link-frequencies", endpoint->link_frequencies, - endpoint->nr_of_link_frequencies); - if (rval < 0) - goto out_err; - } - - return endpoint; - -out_err: - v4l2_of_free_endpoint(endpoint); - return ERR_PTR(rval); -} -EXPORT_SYMBOL(v4l2_of_alloc_parse_endpoint); - -/** - * v4l2_of_parse_link() - parse a link between two endpoints - * @node: pointer to the endpoint at the local end of the link - * @link: pointer to the V4L2 OF link data structure - * - * Fill the link structure with the local and remote nodes and port numbers. - * The local_node and remote_node fields are set to point to the local and - * remote port's parent nodes respectively (the port parent node being the - * parent node of the port node if that node isn't a 'ports' node, or the - * grand-parent node of the port node otherwise). - * - * A reference is taken to both the local and remote nodes, the caller must use - * v4l2_of_put_link() to drop the references when done with the link. - * - * Return: 0 on success, or -ENOLINK if the remote endpoint can't be found. - */ -int v4l2_of_parse_link(const struct device_node *node, - struct v4l2_of_link *link) -{ - struct device_node *np; - - memset(link, 0, sizeof(*link)); - - np = of_get_parent(node); - of_property_read_u32(np, "reg", &link->local_port); - np = of_get_next_parent(np); - if (of_node_cmp(np->name, "ports") == 0) - np = of_get_next_parent(np); - link->local_node = np; - - np = of_parse_phandle(node, "remote-endpoint", 0); - if (!np) { - of_node_put(link->local_node); - return -ENOLINK; - } - - np = of_get_parent(np); - of_property_read_u32(np, "reg", &link->remote_port); - np = of_get_next_parent(np); - if (of_node_cmp(np->name, "ports") == 0) - np = of_get_next_parent(np); - link->remote_node = np; - - return 0; -} -EXPORT_SYMBOL(v4l2_of_parse_link); - -/** - * v4l2_of_put_link() - drop references to nodes in a link - * @link: pointer to the V4L2 OF link data structure - * - * Drop references to the local and remote nodes in the link. This function must - * be called on every link parsed with v4l2_of_parse_link(). - */ -void v4l2_of_put_link(struct v4l2_of_link *link) -{ - of_node_put(link->local_node); - of_node_put(link->remote_node); -} -EXPORT_SYMBOL(v4l2_of_put_link); diff --git a/drivers/media/v4l2-core/videobuf2-core.c b/drivers/media/v4l2-core/videobuf2-core.c index c0175ea7e7ad..3107e2196e54 100644 --- a/drivers/media/v4l2-core/videobuf2-core.c +++ b/drivers/media/v4l2-core/videobuf2-core.c @@ -956,9 +956,9 @@ void vb2_discard_done(struct vb2_queue *q) EXPORT_SYMBOL_GPL(vb2_discard_done); /** - * __qbuf_mmap() - handle qbuf of an MMAP buffer + * __prepare_mmap() - prepare an MMAP buffer */ -static int __qbuf_mmap(struct vb2_buffer *vb, const void *pb) +static int __prepare_mmap(struct vb2_buffer *vb, const void *pb) { int ret = 0; @@ -969,9 +969,9 @@ static int __qbuf_mmap(struct vb2_buffer *vb, const void *pb) } /** - * __qbuf_userptr() - handle qbuf of a USERPTR buffer + * __prepare_userptr() - prepare a USERPTR buffer */ -static int __qbuf_userptr(struct vb2_buffer *vb, const void *pb) +static int __prepare_userptr(struct vb2_buffer *vb, const void *pb) { struct vb2_plane planes[VB2_MAX_PLANES]; struct vb2_queue *q = vb->vb2_queue; @@ -1087,9 +1087,9 @@ err: } /** - * __qbuf_dmabuf() - handle qbuf of a DMABUF buffer + * __prepare_dmabuf() - prepare a DMABUF buffer */ -static int __qbuf_dmabuf(struct vb2_buffer *vb, const void *pb) +static int __prepare_dmabuf(struct vb2_buffer *vb, const void *pb) { struct vb2_plane planes[VB2_MAX_PLANES]; struct vb2_queue *q = vb->vb2_queue; @@ -1227,23 +1227,19 @@ err: static void __enqueue_in_driver(struct vb2_buffer *vb) { struct vb2_queue *q = vb->vb2_queue; - unsigned int plane; vb->state = VB2_BUF_STATE_ACTIVE; atomic_inc(&q->owned_by_drv_count); trace_vb2_buf_queue(q, vb); - /* sync buffers */ - for (plane = 0; plane < vb->num_planes; ++plane) - call_void_memop(vb, prepare, vb->planes[plane].mem_priv); - call_void_vb_qop(vb, buf_queue, vb); } static int __buf_prepare(struct vb2_buffer *vb, const void *pb) { struct vb2_queue *q = vb->vb2_queue; + unsigned int plane; int ret; if (q->error) { @@ -1255,24 +1251,32 @@ static int __buf_prepare(struct vb2_buffer *vb, const void *pb) switch (q->memory) { case VB2_MEMORY_MMAP: - ret = __qbuf_mmap(vb, pb); + ret = __prepare_mmap(vb, pb); break; case VB2_MEMORY_USERPTR: - ret = __qbuf_userptr(vb, pb); + ret = __prepare_userptr(vb, pb); break; case VB2_MEMORY_DMABUF: - ret = __qbuf_dmabuf(vb, pb); + ret = __prepare_dmabuf(vb, pb); break; default: WARN(1, "Invalid queue type\n"); ret = -EINVAL; } - if (ret) + if (ret) { dprintk(1, "buffer preparation failed: %d\n", ret); - vb->state = ret ? VB2_BUF_STATE_DEQUEUED : VB2_BUF_STATE_PREPARED; + vb->state = VB2_BUF_STATE_DEQUEUED; + return ret; + } - return ret; + /* sync buffers */ + for (plane = 0; plane < vb->num_planes; ++plane) + call_void_memop(vb, prepare, vb->planes[plane].mem_priv); + + vb->state = VB2_BUF_STATE_PREPARED; + + return 0; } int vb2_core_prepare_buf(struct vb2_queue *q, unsigned int index, void *pb) diff --git a/drivers/staging/media/Kconfig b/drivers/staging/media/Kconfig index dbda4d9a08e7..f8c25ee082ef 100644 --- a/drivers/staging/media/Kconfig +++ b/drivers/staging/media/Kconfig @@ -27,6 +27,8 @@ source "drivers/staging/media/cxd2099/Kconfig" source "drivers/staging/media/davinci_vpfe/Kconfig" +source "drivers/staging/media/imx/Kconfig" + source "drivers/staging/media/omap4iss/Kconfig" # Keep LIRC at the end, as it has sub-menus diff --git a/drivers/staging/media/Makefile b/drivers/staging/media/Makefile index c04600c81264..ac090c5fce30 100644 --- a/drivers/staging/media/Makefile +++ b/drivers/staging/media/Makefile @@ -1,5 +1,6 @@ obj-$(CONFIG_I2C_BCM2048) += bcm2048/ obj-$(CONFIG_DVB_CXD2099) += cxd2099/ +obj-$(CONFIG_VIDEO_IMX_MEDIA) += imx/ obj-$(CONFIG_LIRC_STAGING) += lirc/ obj-$(CONFIG_VIDEO_DM365_VPFE) += davinci_vpfe/ obj-$(CONFIG_VIDEO_OMAP4) += omap4iss/ diff --git a/drivers/staging/media/atomisp/i2c/Makefile b/drivers/staging/media/atomisp/i2c/Makefile index 466517c7c8e6..be13fab92175 100644 --- a/drivers/staging/media/atomisp/i2c/Makefile +++ b/drivers/staging/media/atomisp/i2c/Makefile @@ -19,3 +19,9 @@ obj-$(CONFIG_VIDEO_AP1302) += ap1302.o obj-$(CONFIG_VIDEO_LM3554) += lm3554.o +# HACK! While this driver is in bad shape, don't enable several warnings +# that would be otherwise enabled with W=1 +ccflags-y += $(call cc-disable-warning, unused-but-set-variable) +ccflags-y += $(call cc-disable-warning, unused-const-variable) +ccflags-y += $(call cc-disable-warning, missing-prototypes) +ccflags-y += $(call cc-disable-warning, missing-declarations) diff --git a/drivers/staging/media/atomisp/i2c/gc0310.c b/drivers/staging/media/atomisp/i2c/gc0310.c index 1ec616a15086..350fd7fd5b86 100644 --- a/drivers/staging/media/atomisp/i2c/gc0310.c +++ b/drivers/staging/media/atomisp/i2c/gc0310.c @@ -1455,6 +1455,7 @@ out_free: static struct acpi_device_id gc0310_acpi_match[] = { {"XXGC0310"}, + {"INT0310"}, {}, }; diff --git a/drivers/staging/media/atomisp/i2c/imx/Makefile b/drivers/staging/media/atomisp/i2c/imx/Makefile index 6b13a3a66e49..b6578f09546e 100644 --- a/drivers/staging/media/atomisp/i2c/imx/Makefile +++ b/drivers/staging/media/atomisp/i2c/imx/Makefile @@ -4,3 +4,10 @@ imx1x5-objs := imx.o drv201.o ad5816g.o dw9714.o dw9719.o dw9718.o vcm.o otp.o o ov8858_driver-objs := ../ov8858.o dw9718.o vcm.o obj-$(CONFIG_VIDEO_OV8858) += ov8858_driver.o + +# HACK! While this driver is in bad shape, don't enable several warnings +# that would be otherwise enabled with W=1 +ccflags-y += $(call cc-disable-warning, unused-but-set-variable) +ccflags-y += $(call cc-disable-warning, unused-const-variable) +ccflags-y += $(call cc-disable-warning, missing-prototypes) +ccflags-y += $(call cc-disable-warning, missing-declarations) diff --git a/drivers/staging/media/atomisp/i2c/lm3554.c b/drivers/staging/media/atomisp/i2c/lm3554.c index dd9c9c3ffff7..2b170c07aaba 100644 --- a/drivers/staging/media/atomisp/i2c/lm3554.c +++ b/drivers/staging/media/atomisp/i2c/lm3554.c @@ -497,7 +497,7 @@ static const struct v4l2_ctrl_ops ctrl_ops = { .g_volatile_ctrl = lm3554_g_volatile_ctrl }; -struct v4l2_ctrl_config lm3554_controls[] = { +static const struct v4l2_ctrl_config lm3554_controls[] = { { .ops = &ctrl_ops, .id = V4L2_CID_FLASH_TIMEOUT, @@ -825,7 +825,7 @@ static int lm3554_gpio_uninit(struct i2c_client *client) return 0; } -void *lm3554_platform_data_func(struct i2c_client *client) +static void *lm3554_platform_data_func(struct i2c_client *client) { static struct lm3554_platform_data platform_data; diff --git a/drivers/staging/media/atomisp/i2c/mt9m114.c b/drivers/staging/media/atomisp/i2c/mt9m114.c index ced175c268d1..3fa915313e53 100644 --- a/drivers/staging/media/atomisp/i2c/mt9m114.c +++ b/drivers/staging/media/atomisp/i2c/mt9m114.c @@ -1499,7 +1499,7 @@ static struct v4l2_ctrl_config mt9m114_controls[] = { .type = V4L2_CTRL_TYPE_MENU, .min = 0, .max = 3, - .step = 1, + .step = 0, .def = 1, .flags = 0, }, diff --git a/drivers/staging/media/atomisp/i2c/ov2680.c b/drivers/staging/media/atomisp/i2c/ov2680.c index 566091035c64..3cabfe54c669 100644 --- a/drivers/staging/media/atomisp/i2c/ov2680.c +++ b/drivers/staging/media/atomisp/i2c/ov2680.c @@ -885,11 +885,12 @@ static int gpio_ctrl(struct v4l2_subdev *sd, bool flag) if (flag) { ret = dev->platform_data->gpio0_ctrl(sd, 1); usleep_range(10000, 15000); - ret |= dev->platform_data->gpio1_ctrl(sd, 1); + /* Ignore return from second gpio, it may not be there */ + dev->platform_data->gpio1_ctrl(sd, 1); usleep_range(10000, 15000); } else { - ret = dev->platform_data->gpio1_ctrl(sd, 0); - ret |= dev->platform_data->gpio0_ctrl(sd, 0); + dev->platform_data->gpio1_ctrl(sd, 0); + ret = dev->platform_data->gpio0_ctrl(sd, 0); } return ret; } @@ -1190,9 +1191,8 @@ static int ov2680_detect(struct i2c_client *client) OV2680_SC_CMMN_SUB_ID, &high); revision = (u8) high & 0x0f; - dev_err(&client->dev, "sensor_revision id = 0x%x\n", id); - dev_err(&client->dev, "detect ov2680 success\n"); - dev_err(&client->dev, "################5##########\n"); + dev_info(&client->dev, "sensor_revision id = 0x%x\n", id); + return 0; } @@ -1447,8 +1447,6 @@ static int ov2680_probe(struct i2c_client *client, void *pdata; unsigned int i; - printk("++++ov2680_probe++++\n"); - dev_info(&client->dev, "++++ov2680_probe++++\n"); dev = kzalloc(sizeof(*dev), GFP_KERNEL); if (!dev) { dev_err(&client->dev, "out of memory\n"); @@ -1521,6 +1519,7 @@ out_free: static struct acpi_device_id ov2680_acpi_match[] = { {"XXOV2680"}, + {"OVTI2680"}, {}, }; MODULE_DEVICE_TABLE(acpi, ov2680_acpi_match); diff --git a/drivers/staging/media/atomisp/i2c/ov5693/Makefile b/drivers/staging/media/atomisp/i2c/ov5693/Makefile index c9c0e1245858..4e3833aaec05 100644 --- a/drivers/staging/media/atomisp/i2c/ov5693/Makefile +++ b/drivers/staging/media/atomisp/i2c/ov5693/Makefile @@ -1 +1,8 @@ obj-$(CONFIG_VIDEO_OV5693) += ov5693.o + +# HACK! While this driver is in bad shape, don't enable several warnings +# that would be otherwise enabled with W=1 +ccflags-y += $(call cc-disable-warning, unused-but-set-variable) +ccflags-y += $(call cc-disable-warning, unused-const-variable) +ccflags-y += $(call cc-disable-warning, missing-prototypes) +ccflags-y += $(call cc-disable-warning, missing-declarations) diff --git a/drivers/staging/media/atomisp/i2c/ov5693/ov5693.c b/drivers/staging/media/atomisp/i2c/ov5693/ov5693.c index 5e9dafe7cc32..d6447398f5ef 100644 --- a/drivers/staging/media/atomisp/i2c/ov5693/ov5693.c +++ b/drivers/staging/media/atomisp/i2c/ov5693/ov5693.c @@ -706,7 +706,7 @@ static int ov5693_read_otp_reg_array(struct i2c_client *client, u16 size, { u16 index; int ret; - u16 *pVal = 0; + u16 *pVal = NULL; for (index = 0; index <= size; index++) { pVal = (u16 *) (buf + index); diff --git a/drivers/staging/media/atomisp/pci/atomisp2/Makefile b/drivers/staging/media/atomisp/pci/atomisp2/Makefile index f126a89a08e9..726eaa293c55 100644 --- a/drivers/staging/media/atomisp/pci/atomisp2/Makefile +++ b/drivers/staging/media/atomisp/pci/atomisp2/Makefile @@ -108,7 +108,6 @@ atomisp-objs += \ css2400/sh_css_metadata.o \ css2400/base/refcount/src/refcount.o \ css2400/base/circbuf/src/circbuf.o \ - css2400/sh_css_irq.o \ css2400/camera/pipe/src/pipe_binarydesc.o \ css2400/camera/pipe/src/pipe_util.o \ css2400/camera/pipe/src/pipe_stagedesc.o \ @@ -353,3 +352,9 @@ DEFINES += -DSYSTEM_hive_isp_css_2400_system -DISP2400 ccflags-y += $(INCLUDES) $(DEFINES) -fno-common +# HACK! While this driver is in bad shape, don't enable several warnings +# that would be otherwise enabled with W=1 +ccflags-y += -Wno-unused-const-variable -Wno-missing-prototypes \ + -Wno-unused-but-set-variable -Wno-missing-declarations \ + -Wno-suggest-attribute=format -Wno-missing-prototypes \ + -Wno-implicit-fallthrough diff --git a/drivers/staging/media/atomisp/pci/atomisp2/atomisp_compat_css20.c b/drivers/staging/media/atomisp/pci/atomisp2/atomisp_compat_css20.c index b830b241e2e6..ad2c610d2ce3 100644 --- a/drivers/staging/media/atomisp/pci/atomisp2/atomisp_compat_css20.c +++ b/drivers/staging/media/atomisp/pci/atomisp2/atomisp_compat_css20.c @@ -2506,7 +2506,6 @@ static void __configure_capture_pp_input(struct atomisp_sub_device *asd, struct ia_css_pipe_extra_config *pipe_extra_configs = &stream_env->pipe_extra_configs[pipe_id]; unsigned int hor_ds_factor = 0, ver_ds_factor = 0; -#define CEIL_DIV(a, b) ((b) ? ((a) + (b) - 1) / (b) : 0) if (width == 0 && height == 0) return; diff --git a/drivers/staging/media/atomisp/pci/atomisp2/atomisp_fops.c b/drivers/staging/media/atomisp/pci/atomisp2/atomisp_fops.c index 7ce8803cf6f9..c151c848cf8f 100644 --- a/drivers/staging/media/atomisp/pci/atomisp2/atomisp_fops.c +++ b/drivers/staging/media/atomisp/pci/atomisp2/atomisp_fops.c @@ -130,9 +130,9 @@ static int atomisp_q_one_metadata_buffer(struct atomisp_sub_device *asd, return 0; } -int atomisp_q_one_s3a_buffer(struct atomisp_sub_device *asd, - enum atomisp_input_stream_id stream_id, - enum atomisp_css_pipe_id css_pipe_id) +static int atomisp_q_one_s3a_buffer(struct atomisp_sub_device *asd, + enum atomisp_input_stream_id stream_id, + enum atomisp_css_pipe_id css_pipe_id) { struct atomisp_s3a_buf *s3a_buf; struct list_head *s3a_list; @@ -172,9 +172,9 @@ int atomisp_q_one_s3a_buffer(struct atomisp_sub_device *asd, return 0; } -int atomisp_q_one_dis_buffer(struct atomisp_sub_device *asd, - enum atomisp_input_stream_id stream_id, - enum atomisp_css_pipe_id css_pipe_id) +static int atomisp_q_one_dis_buffer(struct atomisp_sub_device *asd, + enum atomisp_input_stream_id stream_id, + enum atomisp_css_pipe_id css_pipe_id) { struct atomisp_dis_buf *dis_buf; unsigned long irqflags; @@ -744,7 +744,7 @@ static void atomisp_subdev_init_struct(struct atomisp_sub_device *asd) /* * file operation functions */ -unsigned int atomisp_subdev_users(struct atomisp_sub_device *asd) +static unsigned int atomisp_subdev_users(struct atomisp_sub_device *asd) { return asd->video_out_preview.users + asd->video_out_vf.users + diff --git a/drivers/staging/media/atomisp/pci/atomisp2/atomisp_ioctl.c b/drivers/staging/media/atomisp/pci/atomisp2/atomisp_ioctl.c index 6064bb823a47..aa0526ebaff1 100644 --- a/drivers/staging/media/atomisp/pci/atomisp2/atomisp_ioctl.c +++ b/drivers/staging/media/atomisp/pci/atomisp2/atomisp_ioctl.c @@ -683,7 +683,7 @@ static int atomisp_s_input(struct file *file, void *fh, unsigned int input) int ret; rt_mutex_lock(&isp->mutex); - if (input >= ATOM_ISP_MAX_INPUTS || input > isp->input_cnt) { + if (input >= ATOM_ISP_MAX_INPUTS || input >= isp->input_cnt) { dev_dbg(isp->dev, "input_cnt: %d\n", isp->input_cnt); ret = -EINVAL; goto error; diff --git a/drivers/staging/media/atomisp/pci/atomisp2/atomisp_tpg.c b/drivers/staging/media/atomisp/pci/atomisp2/atomisp_tpg.c index 996d1bdebad4..48b96048cab4 100644 --- a/drivers/staging/media/atomisp/pci/atomisp2/atomisp_tpg.c +++ b/drivers/staging/media/atomisp/pci/atomisp2/atomisp_tpg.c @@ -56,6 +56,7 @@ static int tpg_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_format *format) { struct v4l2_mbus_framefmt *fmt = &format->format; + if (format->pad) return -EINVAL; /* only raw8 grbg is supported by TPG */ diff --git a/drivers/staging/media/atomisp/pci/atomisp2/atomisp_v4l2.c b/drivers/staging/media/atomisp/pci/atomisp2/atomisp_v4l2.c index e3fdbdba0b34..a543def739fc 100644 --- a/drivers/staging/media/atomisp/pci/atomisp2/atomisp_v4l2.c +++ b/drivers/staging/media/atomisp/pci/atomisp2/atomisp_v4l2.c @@ -51,12 +51,12 @@ /* G-Min addition: pull this in from intel_mid_pm.h */ #define CSTATE_EXIT_LATENCY_C1 1 -static uint skip_fwload = 0; +static uint skip_fwload; module_param(skip_fwload, uint, 0644); MODULE_PARM_DESC(skip_fwload, "Skip atomisp firmware load"); /* set reserved memory pool size in page */ -unsigned int repool_pgnr; +static unsigned int repool_pgnr; module_param(repool_pgnr, uint, 0644); MODULE_PARM_DESC(repool_pgnr, "Set the reserved memory pool size in page (default:0)"); @@ -384,7 +384,7 @@ done: * WA for DDR DVFS enable/disable * By default, ISP will force DDR DVFS 1600MHz before disable DVFS */ -void punit_ddr_dvfs_enable(bool enable) +static void punit_ddr_dvfs_enable(bool enable) { int reg = intel_mid_msgbus_read32(PUNIT_PORT, MRFLD_ISPSSDVFS); int door_bell = 1 << 8; diff --git a/drivers/staging/media/atomisp/pci/atomisp2/css2400/Makefile b/drivers/staging/media/atomisp/pci/atomisp2/css2400/Makefile index 04defaafa02c..ee5631b0e635 100644 --- a/drivers/staging/media/atomisp/pci/atomisp2/css2400/Makefile +++ b/drivers/staging/media/atomisp/pci/atomisp2/css2400/Makefile @@ -1,4 +1,2 @@ ccflags-y += -DISP2400B0 ISP2400B0 := y - -include $(srctree)/$(src)/../Makefile.common diff --git a/drivers/staging/media/atomisp/pci/atomisp2/css2400/hive_isp_css_include/math_support.h b/drivers/staging/media/atomisp/pci/atomisp2/css2400/hive_isp_css_include/math_support.h index 48d84bc0ad9e..f74b405b0f39 100644 --- a/drivers/staging/media/atomisp/pci/atomisp2/css2400/hive_isp_css_include/math_support.h +++ b/drivers/staging/media/atomisp/pci/atomisp2/css2400/hive_isp_css_include/math_support.h @@ -62,15 +62,15 @@ #define MAX(a, b) (((a) > (b)) ? (a) : (b)) #define MIN(a, b) (((a) < (b)) ? (a) : (b)) #ifdef ISP2401 -#define ROUND_DIV(a, b) ((b) ? ((a) + ((b) >> 1)) / (b) : 0) +#define ROUND_DIV(a, b) (((b) != 0) ? ((a) + ((b) >> 1)) / (b) : 0) #endif -#define CEIL_DIV(a, b) ((b) ? ((a) + (b) - 1) / (b) : 0) +#define CEIL_DIV(a, b) (((b) != 0) ? ((a) + (b) - 1) / (b) : 0) #define CEIL_MUL(a, b) (CEIL_DIV(a, b) * (b)) #define CEIL_MUL2(a, b) (((a) + (b) - 1) & ~((b) - 1)) #define CEIL_SHIFT(a, b) (((a) + (1 << (b)) - 1)>>(b)) #define CEIL_SHIFT_MUL(a, b) (CEIL_SHIFT(a, b) << (b)) #ifdef ISP2401 -#define ROUND_HALF_DOWN_DIV(a, b) ((b) ? ((a) + (b / 2) - 1) / (b) : 0) +#define ROUND_HALF_DOWN_DIV(a, b) (((b) != 0) ? ((a) + (b / 2) - 1) / (b) : 0) #define ROUND_HALF_DOWN_MUL(a, b) (ROUND_HALF_DOWN_DIV(a, b) * (b)) #endif diff --git a/drivers/staging/media/atomisp/pci/atomisp2/css2400/hive_isp_css_include/string_support.h b/drivers/staging/media/atomisp/pci/atomisp2/css2400/hive_isp_css_include/string_support.h index 568631698a3d..c53241a7a281 100644 --- a/drivers/staging/media/atomisp/pci/atomisp2/css2400/hive_isp_css_include/string_support.h +++ b/drivers/staging/media/atomisp/pci/atomisp2/css2400/hive_isp_css_include/string_support.h @@ -72,9 +72,8 @@ static size_t strnlen_s( return 0; } - for (ix=0; - ((src_str[ix] != '\0') && (ix< max_len)); - ++ix) /*Nothing else to do*/; + for (ix = 0; ix < max_len && src_str[ix] != '\0'; ix++) + ; /* On Error, it will return src_size == max_len*/ return ix; @@ -118,7 +117,7 @@ STORAGE_CLASS_INLINE int strncpy_s( /* dest_str is big enough for the len */ strncpy(dest_str, src_str, len); - dest_str[len+1] = '\0'; + dest_str[len] = '\0'; return 0; } @@ -158,7 +157,7 @@ STORAGE_CLASS_INLINE int strcpy_s( /* dest_str is big enough for the len */ strncpy(dest_str, src_str, len); - dest_str[len+1] = '\0'; + dest_str[len] = '\0'; return 0; } diff --git a/drivers/staging/media/atomisp/pci/atomisp2/css2400/ia_css_mmu_private.h b/drivers/staging/media/atomisp/pci/atomisp2/css2400/ia_css_mmu_private.h index 7c8500903b5c..1021e4f380a5 100644 --- a/drivers/staging/media/atomisp/pci/atomisp2/css2400/ia_css_mmu_private.h +++ b/drivers/staging/media/atomisp/pci/atomisp2/css2400/ia_css_mmu_private.h @@ -1,4 +1,3 @@ -#ifdef ISP2401 /* * Support for Intel Camera Imaging ISP subsystem. * Copyright (c) 2015, Intel Corporation. @@ -28,4 +27,3 @@ void sh_css_mmu_set_page_table_base_index(hrt_data base_index); #endif /* __IA_CSS_MMU_PRIVATE_H */ -#endif diff --git a/drivers/staging/media/atomisp/pci/atomisp2/css2400/isp/kernels/sdis/sdis_1.0/ia_css_sdis.host.c b/drivers/staging/media/atomisp/pci/atomisp2/css2400/isp/kernels/sdis/sdis_1.0/ia_css_sdis.host.c index 0daab1176865..9478c12abe89 100644 --- a/drivers/staging/media/atomisp/pci/atomisp2/css2400/isp/kernels/sdis/sdis_1.0/ia_css_sdis.host.c +++ b/drivers/staging/media/atomisp/pci/atomisp2/css2400/isp/kernels/sdis/sdis_1.0/ia_css_sdis.host.c @@ -265,9 +265,9 @@ ia_css_translate_dvs_statistics( assert(isp_stats->hor_proj != NULL); assert(isp_stats->ver_proj != NULL); - IA_CSS_ENTER("hproj=%p, vproj=%p, haddr=%x, vaddr=%x", - host_stats->hor_proj, host_stats->ver_proj, - isp_stats->hor_proj, isp_stats->ver_proj); + IA_CSS_ENTER("hproj=%p, vproj=%p, haddr=%p, vaddr=%p", + host_stats->hor_proj, host_stats->ver_proj, + isp_stats->hor_proj, isp_stats->ver_proj); hor_num_isp = host_stats->grid.aligned_height; ver_num_isp = host_stats->grid.aligned_width; diff --git a/drivers/staging/media/atomisp/pci/atomisp2/css2400/isp/kernels/sdis/sdis_2/ia_css_sdis2.host.c b/drivers/staging/media/atomisp/pci/atomisp2/css2400/isp/kernels/sdis/sdis_2/ia_css_sdis2.host.c index 5a0c103e9eb7..9bccb6473154 100644 --- a/drivers/staging/media/atomisp/pci/atomisp2/css2400/isp/kernels/sdis/sdis_2/ia_css_sdis2.host.c +++ b/drivers/staging/media/atomisp/pci/atomisp2/css2400/isp/kernels/sdis/sdis_2/ia_css_sdis2.host.c @@ -213,7 +213,7 @@ ia_css_translate_dvs2_statistics( "hor_coefs.even_real=%p, hor_coefs.even_imag=%p, " "ver_coefs.odd_real=%p, ver_coefs.odd_imag=%p, " "ver_coefs.even_real=%p, ver_coefs.even_imag=%p, " - "haddr=%x, vaddr=%x", + "haddr=%p, vaddr=%p", host_stats->hor_prod.odd_real, host_stats->hor_prod.odd_imag, host_stats->hor_prod.even_real, host_stats->hor_prod.even_imag, host_stats->ver_prod.odd_real, host_stats->ver_prod.odd_imag, diff --git a/drivers/staging/media/atomisp/pci/atomisp2/css2400/isp/kernels/tnr/tnr_1.0/ia_css_tnr.host.c b/drivers/staging/media/atomisp/pci/atomisp2/css2400/isp/kernels/tnr/tnr_1.0/ia_css_tnr.host.c index 804c19ab4485..222a7bd7f176 100644 --- a/drivers/staging/media/atomisp/pci/atomisp2/css2400/isp/kernels/tnr/tnr_1.0/ia_css_tnr.host.c +++ b/drivers/staging/media/atomisp/pci/atomisp2/css2400/isp/kernels/tnr/tnr_1.0/ia_css_tnr.host.c @@ -55,7 +55,7 @@ ia_css_tnr_dump( "tnr_coef", tnr->coef); ia_css_debug_dtrace(level, "\t%-32s = %d\n", "tnr_threshold_Y", tnr->threshold_Y); - ia_css_debug_dtrace(level, "\t%-32s = %d\n" + ia_css_debug_dtrace(level, "\t%-32s = %d\n", "tnr_threshold_C", tnr->threshold_C); } diff --git a/drivers/staging/media/atomisp/pci/atomisp2/css2400/isp/modes/interface/isp_const.h b/drivers/staging/media/atomisp/pci/atomisp2/css2400/isp/modes/interface/isp_const.h index 005eaaa9eb6c..2f215dc2ac32 100644 --- a/drivers/staging/media/atomisp/pci/atomisp2/css2400/isp/modes/interface/isp_const.h +++ b/drivers/staging/media/atomisp/pci/atomisp2/css2400/isp/modes/interface/isp_const.h @@ -398,17 +398,6 @@ more details. * so the calc for the output buffer vmem size is: * ((width[vectors]/num_of_stripes) + 2[vectors]) */ -#if defined(HAS_RES_MGR) -#define MAX_VECTORS_PER_OUTPUT_LINE \ - (CEIL_DIV(CEIL_DIV(ISP_MAX_OUTPUT_WIDTH, ISP_NUM_STRIPES) + ISP_LEFT_PADDING, ISP_VEC_NELEMS) + \ - ITERATOR_VECTOR_INCREMENT) - -#define MAX_VECTORS_PER_INPUT_LINE CEIL_DIV(ISP_MAX_INPUT_WIDTH, ISP_VEC_NELEMS) -#define MAX_VECTORS_PER_INPUT_STRIPE (CEIL_ROUND_DIV_STRIPE(CEIL_DIV(ISP_MAX_INPUT_WIDTH, ISP_VEC_NELEMS) , \ - ISP_NUM_STRIPES, \ - ISP_LEFT_PADDING_VECS) + \ - ITERATOR_VECTOR_INCREMENT) -#else /* !defined(HAS_RES_MGR)*/ #define MAX_VECTORS_PER_OUTPUT_LINE \ CEIL_DIV(CEIL_DIV(ISP_MAX_OUTPUT_WIDTH, ISP_NUM_STRIPES) + ISP_LEFT_PADDING, ISP_VEC_NELEMS) @@ -417,7 +406,6 @@ more details. #define MAX_VECTORS_PER_INPUT_STRIPE CEIL_ROUND_DIV_STRIPE(MAX_VECTORS_PER_INPUT_LINE, \ ISP_NUM_STRIPES, \ ISP_LEFT_PADDING_VECS) -#endif /* HAS_RES_MGR */ /* Add 2 for left croppping */ @@ -470,15 +458,11 @@ more details. #define RAW_BUF_LINES ((ENABLE_RAW_BINNING || ENABLE_FIXED_BAYER_DS) ? 4 : 2) -#if defined(HAS_RES_MGR) -#define RAW_BUF_STRIDE (MAX_VECTORS_PER_INPUT_STRIPE) -#else /* !defined(HAS_RES_MGR) */ #define RAW_BUF_STRIDE \ (BINARY_ID == SH_CSS_BINARY_ID_POST_ISP ? MAX_VECTORS_PER_INPUT_CHUNK : \ ISP_NUM_STRIPES > 1 ? MAX_VECTORS_PER_INPUT_STRIPE+_ISP_EXTRA_PADDING_VECS : \ !ENABLE_CONTINUOUS ? MAX_VECTORS_PER_INPUT_LINE : \ MAX_VECTORS_PER_INPUT_CHUNK) -#endif /* HAS_RES_MGR */ /* [isp vmem] table size[vectors] per line per color (GR,R,B,GB), multiples of NWAY */ diff --git a/drivers/staging/media/atomisp/pci/atomisp2/css2400/isp/modes/interface/isp_exprs.h b/drivers/staging/media/atomisp/pci/atomisp2/css2400/isp/modes/interface/isp_exprs.h index 8b59a8caec52..e625ba62cc15 100644 --- a/drivers/staging/media/atomisp/pci/atomisp2/css2400/isp/modes/interface/isp_exprs.h +++ b/drivers/staging/media/atomisp/pci/atomisp2/css2400/isp/modes/interface/isp_exprs.h @@ -214,24 +214,6 @@ more details. /******* STRIPING-RELATED MACROS *******/ #define NO_STRIPING (ISP_NUM_STRIPES == 1) -#if defined(HAS_RES_MGR) - -#define ISP_OUTPUT_CHUNK_VECS ISP_INTERNAL_WIDTH_VECS - -#if defined(__ISP) -#define VECTORS_PER_LINE ISP_INTERNAL_WIDTH_VECS -#else -#define VECTORS_PER_LINE \ - (NO_STRIPING ? ISP_INTERNAL_WIDTH_VECS \ - : ISP_IO_STRIPE_WIDTH_VECS(ISP_INTERNAL_WIDTH_VECS, ISP_LEFT_PADDING_VECS, ISP_NUM_STRIPES, ISP_MIN_STRIPE_WIDTH) ) -#endif - -#define VECTORS_PER_INPUT_LINE \ - (NO_STRIPING ? ISP_INPUT_WIDTH_VECS \ - : ISP_IO_STRIPE_WIDTH_VECS(ISP_INPUT_WIDTH_VECS, ISP_LEFT_PADDING_VECS, ISP_NUM_STRIPES, ISP_MIN_STRIPE_WIDTH) ) - -#else - #define ISP_OUTPUT_CHUNK_VECS \ (NO_STRIPING ? CEIL_DIV_CHUNKS(ISP_OUTPUT_VECS_EXTRA_CROP, OUTPUT_NUM_CHUNKS) \ : ISP_IO_STRIPE_WIDTH_VECS(ISP_OUTPUT_VECS_EXTRA_CROP, ISP_LEFT_PADDING_VECS, ISP_NUM_STRIPES, ISP_MIN_STRIPE_WIDTH) ) @@ -244,7 +226,6 @@ more details. (NO_STRIPING ? ISP_INPUT_WIDTH_VECS \ : ISP_IO_STRIPE_WIDTH_VECS(ISP_INPUT_WIDTH_VECS, ISP_LEFT_PADDING_VECS, ISP_NUM_STRIPES, ISP_MIN_STRIPE_WIDTH)+_ISP_EXTRA_PADDING_VECS) -#endif #define ISP_MAX_VF_OUTPUT_STRIPE_VECS \ (NO_STRIPING ? ISP_MAX_VF_OUTPUT_VECS \ @@ -282,11 +263,7 @@ more details. #define OUTPUT_VECTORS_PER_CHUNK CEIL_DIV_CHUNKS(VECTORS_PER_LINE,OUTPUT_NUM_CHUNKS) /* should be even?? */ -#if !defined(HAS_RES_MGR) #define OUTPUT_C_VECTORS_PER_CHUNK CEIL_DIV(OUTPUT_VECTORS_PER_CHUNK, 2) -#else -#define OUTPUT_C_VECTORS_PER_CHUNK CEIL_DIV(MAX_VECTORS_PER_CHUNK, 2) -#endif #ifndef ISP2401 /**** SCTBL defs *******/ diff --git a/drivers/staging/media/atomisp/pci/atomisp2/css2400/runtime/binary/src/binary.c b/drivers/staging/media/atomisp/pci/atomisp2/css2400/runtime/binary/src/binary.c index a8b93a756e41..9f8a125f0d74 100644 --- a/drivers/staging/media/atomisp/pci/atomisp2/css2400/runtime/binary/src/binary.c +++ b/drivers/staging/media/atomisp/pci/atomisp2/css2400/runtime/binary/src/binary.c @@ -36,10 +36,6 @@ #endif #include "camera/pipe/interface/ia_css_pipe_binarydesc.h" -#if defined(HAS_RES_MGR) -#include <components/resolutions_mgr/src/host/resolutions_mgr.host.h> -#include <components/acc_cluster/acc_dvs_stat/host/dvs_stat.host.h> -#endif #include "memory_access.h" @@ -110,10 +106,6 @@ ia_css_binary_internal_res(const struct ia_css_frame_info *in_info, internal_res->height = __ISP_INTERNAL_HEIGHT(isp_tmp_internal_height, info->pipeline.top_cropping, binary_dvs_env.height); -#if defined(HAS_RES_MGR) - internal_res->height = (bds_out_info == NULL) ? internal_res->height : bds_out_info->res.height; - internal_res->width = (bds_out_info == NULL) ? internal_res->width: bds_out_info->res.width; -#endif } #ifndef ISP2401 @@ -787,25 +779,6 @@ ia_css_binary_dvs_stat_grid_info( struct ia_css_grid_info *info, struct ia_css_pipe *pipe) { -#if defined(HAS_RES_MGR) - struct ia_css_dvs_stat_grid_info *dvs_stat_info; - unsigned int i; - - assert(binary != NULL); - assert(info != NULL); - dvs_stat_info = &info->dvs_grid.dvs_stat_grid_info; - - if (binary->info->sp.enable.dvs_stats) { - for (i = 0; i < IA_CSS_SKC_DVS_STAT_NUM_OF_LEVELS; i++) { - dvs_stat_info->grd_cfg[i].grd_start.enable = 1; - } - ia_css_dvs_stat_grid_calculate(pipe, dvs_stat_info); - } - else { - memset(dvs_stat_info, 0, sizeof(struct ia_css_dvs_stat_grid_info)); - } - -#endif (void)pipe; sh_css_binary_common_grid_info(binary, info); return; @@ -1088,9 +1061,6 @@ binary_in_frame_padded_width(int in_frame_width, /* in other cases, the left padding pixels are always 128 */ nr_of_left_paddings = 2*ISP_VEC_NELEMS; #endif -#if defined(HAS_RES_MGR) - (void)dvs_env_width; -#endif if (need_scaling) { /* In SDV use-case, we need to match left-padding of * primary and the video binary. */ @@ -1101,9 +1071,7 @@ binary_in_frame_padded_width(int in_frame_width, 2*ISP_VEC_NELEMS); } else { /* Different than before, we do left&right padding. */ -#if !defined(HAS_RES_MGR) /* dvs env is included already */ in_frame_width += dvs_env_width; -#endif rval = CEIL_MUL(in_frame_width + (left_cropping ? nr_of_left_paddings : 0), @@ -1214,10 +1182,8 @@ ia_css_binary_fill_info(const struct ia_css_binary_xinfo *xinfo, binary->in_frame_info.res.width = in_info->res.width + info->pipeline.left_cropping; binary->in_frame_info.res.height = in_info->res.height + info->pipeline.top_cropping; -#if !defined(HAS_RES_MGR) /* dvs env is included already */ binary->in_frame_info.res.width += dvs_env_width; binary->in_frame_info.res.height += dvs_env_height; -#endif binary->in_frame_info.padded_width = binary_in_frame_padded_width(in_info->res.width, @@ -1658,7 +1624,7 @@ ia_css_binary_find(struct ia_css_binary_descr *descr, candidate->internal.max_height); continue; } - if (!candidate->enable.ds && need_ds & !(xcandidate->num_output_pins > 1)) { + if (!candidate->enable.ds && need_ds && !(xcandidate->num_output_pins > 1)) { ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE, "ia_css_binary_find() [%d] continue: !%d && %d\n", __LINE__, candidate->enable.ds, (int)need_ds); diff --git a/drivers/staging/media/atomisp/pci/atomisp2/css2400/runtime/bufq/src/bufq.c b/drivers/staging/media/atomisp/pci/atomisp2/css2400/runtime/bufq/src/bufq.c index ed33d4c4c84a..5d40afd482f5 100644 --- a/drivers/staging/media/atomisp/pci/atomisp2/css2400/runtime/bufq/src/bufq.c +++ b/drivers/staging/media/atomisp/pci/atomisp2/css2400/runtime/bufq/src/bufq.c @@ -239,7 +239,7 @@ static ia_css_queue_t *bufq_get_qhandle( enum sh_css_queue_id id, int thread) { - ia_css_queue_t *q = 0; + ia_css_queue_t *q = NULL; switch (type) { case sh_css_host2sp_buffer_queue: diff --git a/drivers/staging/media/atomisp/pci/atomisp2/css2400/runtime/debug/interface/ia_css_debug.h b/drivers/staging/media/atomisp/pci/atomisp2/css2400/runtime/debug/interface/ia_css_debug.h index be7df3a30c21..91c105cc6204 100644 --- a/drivers/staging/media/atomisp/pci/atomisp2/css2400/runtime/debug/interface/ia_css_debug.h +++ b/drivers/staging/media/atomisp/pci/atomisp2/css2400/runtime/debug/interface/ia_css_debug.h @@ -137,6 +137,7 @@ ia_css_debug_vdtrace(unsigned int level, const char *fmt, va_list args) sh_css_vprint(fmt, args); } +__printf(2, 3) extern void ia_css_debug_dtrace(unsigned int level, const char *fmt, ...); /*! @brief Dump sp thread's stack contents diff --git a/drivers/staging/media/atomisp/pci/atomisp2/css2400/runtime/debug/src/ia_css_debug.c b/drivers/staging/media/atomisp/pci/atomisp2/css2400/runtime/debug/src/ia_css_debug.c index 030810bd0878..0fa7cb2423d8 100644 --- a/drivers/staging/media/atomisp/pci/atomisp2/css2400/runtime/debug/src/ia_css_debug.c +++ b/drivers/staging/media/atomisp/pci/atomisp2/css2400/runtime/debug/src/ia_css_debug.c @@ -176,7 +176,6 @@ void ia_css_debug_dtrace(unsigned int level, const char *fmt, ...) va_end(ap); } -#if !defined(HRT_UNSCHED) static void debug_dump_long_array_formatted( const sp_ID_t sp_id, hrt_address stack_sp_addr, @@ -249,12 +248,6 @@ void ia_css_debug_dump_sp_stack_info(void) { debug_dump_sp_stack_info(SP0_ID); } -#else -/* Empty def for crun */ -void ia_css_debug_dump_sp_stack_info(void) -{ -} -#endif /* #if !HRT_UNSCHED */ void ia_css_debug_set_dtrace_level(const unsigned int trace_level) @@ -3148,8 +3141,8 @@ ia_css_debug_dump_pipe_config( ia_css_debug_dump_frame_info(&config->vf_output_info[i], "vf_output_info"); } - ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE, "acc_extension: 0x%x\n", - config->acc_extension); + ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE, "acc_extension: %p\n", + config->acc_extension); ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE, "num_acc_stages: %d\n", config->num_acc_stages); ia_css_debug_dump_capture_config(&config->default_capture_config); @@ -3179,7 +3172,7 @@ ia_css_debug_dump_stream_config_source( ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE, "timeout: %d\n", config->source.port.timeout); ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE, "compression: %d\n", - config->source.port.compression); + config->source.port.compression.type); break; case IA_CSS_INPUT_MODE_TPG: ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE, "source.tpg\n"); diff --git a/drivers/staging/media/atomisp/pci/atomisp2/css2400/runtime/spctrl/src/spctrl.c b/drivers/staging/media/atomisp/pci/atomisp2/css2400/runtime/spctrl/src/spctrl.c index b36d7b00ebe8..d9178e80dab2 100644 --- a/drivers/staging/media/atomisp/pci/atomisp2/css2400/runtime/spctrl/src/spctrl.c +++ b/drivers/staging/media/atomisp/pci/atomisp2/css2400/runtime/spctrl/src/spctrl.c @@ -57,17 +57,11 @@ enum ia_css_err ia_css_spctrl_load_fw(sp_ID_t sp_id, hrt_vaddress code_addr = mmgr_NULL; struct ia_css_sp_init_dmem_cfg *init_dmem_cfg; - if ((sp_id >= N_SP_ID) || (spctrl_cfg == 0)) + if ((sp_id >= N_SP_ID) || (spctrl_cfg == NULL)) return IA_CSS_ERR_INVALID_ARGUMENTS; spctrl_cofig_info[sp_id].code_addr = mmgr_NULL; -#if defined(HRT_UNSCHED) - (void)init_dmem_cfg; - code_addr = mmgr_malloc(1); - if (code_addr == mmgr_NULL) - return IA_CSS_ERR_CANNOT_ALLOCATE_MEMORY; -#else init_dmem_cfg = &spctrl_cofig_info[sp_id].dmem_config; init_dmem_cfg->dmem_data_addr = spctrl_cfg->dmem_data_addr; init_dmem_cfg->dmem_bss_addr = spctrl_cfg->dmem_bss_addr; @@ -104,7 +98,7 @@ enum ia_css_err ia_css_spctrl_load_fw(sp_ID_t sp_id, code_addr = mmgr_NULL; return IA_CSS_ERR_INTERNAL_ERROR; } -#endif + spctrl_cofig_info[sp_id].sp_entry = spctrl_cfg->sp_entry; spctrl_cofig_info[sp_id].code_addr = code_addr; spctrl_cofig_info[sp_id].program_name = spctrl_cfg->program_name; diff --git a/drivers/staging/media/atomisp/pci/atomisp2/css2400/sh_css.c b/drivers/staging/media/atomisp/pci/atomisp2/css2400/sh_css.c index 73c76583610a..471f2be974e2 100644 --- a/drivers/staging/media/atomisp/pci/atomisp2/css2400/sh_css.c +++ b/drivers/staging/media/atomisp/pci/atomisp2/css2400/sh_css.c @@ -64,7 +64,7 @@ #include "input_system.h" #endif #include "mmu_device.h" /* mmu_set_page_table_base_index(), ... */ -//#include "ia_css_mmu_private.h" /* sh_css_mmu_set_page_table_base_index() */ +#include "ia_css_mmu_private.h" /* sh_css_mmu_set_page_table_base_index() */ #include "gdc_device.h" /* HRT_GDC_N */ #include "dma.h" /* dma_set_max_burst_size() */ #include "irq.h" /* virq */ @@ -98,18 +98,8 @@ static int thread_alive; #include "isp/modes/interface/input_buf.isp.h" -#if defined(HAS_BL) -#include "support/bootloader/interface/ia_css_blctrl.h" -#endif -#if defined(HAS_RES_MGR) -#include "components/acc_cluster/gen/host/acc_cluster.host.h" -#endif - /* Name of the sp program: should not be built-in */ #define SP_PROG_NAME "sp" -#if defined(HAS_BL) -#define BL_PROG_NAME "bootloader" -#endif /* Size of Refcount List */ #define REFCOUNT_SIZE 1000 @@ -252,11 +242,6 @@ ia_css_reset_defaults(struct sh_css* css); static void sh_css_init_host_sp_control_vars(void); -#ifndef ISP2401 -static void -sh_css_mmu_set_page_table_base_index(hrt_data base_index); - -#endif static enum ia_css_err set_num_primary_stages(unsigned int *num, enum ia_css_pipe_version version); static bool @@ -385,13 +370,8 @@ sh_css_hmm_buffer_record_uninit(void); static void sh_css_hmm_buffer_record_reset(struct sh_css_hmm_buffer_record *buffer_record); -#ifndef ISP2401 -static bool -sh_css_hmm_buffer_record_acquire(struct ia_css_rmgr_vbuf_handle *h_vbuf, -#else static struct sh_css_hmm_buffer_record *sh_css_hmm_buffer_record_acquire(struct ia_css_rmgr_vbuf_handle *h_vbuf, -#endif enum ia_css_buffer_type type, hrt_address kernel_ptr); @@ -1475,30 +1455,17 @@ static void start_pipe( copy_ovrd, input_mode, &me->stream->config.metadata_config, -#ifndef ISP2401 &me->stream->info.metadata_info -#else - &me->stream->info.metadata_info, -#endif #if !defined(HAS_NO_INPUT_SYSTEM) -#ifndef ISP2401 - , (input_mode==IA_CSS_INPUT_MODE_MEMORY)? -#else - (input_mode == IA_CSS_INPUT_MODE_MEMORY) ? -#endif + ,(input_mode==IA_CSS_INPUT_MODE_MEMORY) ? (mipi_port_ID_t)0 : -#ifndef ISP2401 me->stream->config.source.port.port -#else - me->stream->config.source.port.port, #endif +#ifdef ISP2401 + ,&me->config.internal_frame_origin_bqs_on_sctbl, + me->stream->isp_params_configs #endif -#ifndef ISP2401 - ); -#else - &me->config.internal_frame_origin_bqs_on_sctbl, - me->stream->isp_params_configs); -#endif + ); if (me->config.mode != IA_CSS_PIPE_MODE_COPY) { struct ia_css_pipeline_stage *stage; @@ -1571,34 +1538,7 @@ enable_interrupts(enum ia_css_irq_type irq_type) } #endif -#if defined(HAS_BL) -static bool sh_css_setup_blctrl_config(const struct ia_css_fw_info *fw, - const char *program, - ia_css_blctrl_cfg *blctrl_cfg) -{ - if((fw == NULL)||(blctrl_cfg == NULL)) - return false; - blctrl_cfg->bl_entry = 0; - blctrl_cfg->program_name = (char *)(program); - -#if !defined(HRT_UNSCHED) - blctrl_cfg->ddr_data_offset = fw->blob.data_source; - blctrl_cfg->dmem_data_addr = fw->blob.data_target; - blctrl_cfg->dmem_bss_addr = fw->blob.bss_target; - blctrl_cfg->data_size = fw->blob.data_size ; - blctrl_cfg->bss_size = fw->blob.bss_size; - blctrl_cfg->blctrl_state_dmem_addr = fw->info.bl.sw_state; - blctrl_cfg->blctrl_dma_cmd_list = fw->info.bl.dma_cmd_list; - blctrl_cfg->blctrl_nr_of_dma_cmds = fw->info.bl.num_dma_cmds; - - blctrl_cfg->code_size = fw->blob.size; - blctrl_cfg->code = fw->blob.code; - blctrl_cfg->bl_entry = fw->info.bl.bl_entry; /* entry function ptr on Bootloader */ -#endif - return true; -} -#endif static bool sh_css_setup_spctrl_config(const struct ia_css_fw_info *fw, const char * program, ia_css_spctrl_cfg *spctrl_cfg) @@ -1608,7 +1548,6 @@ static bool sh_css_setup_spctrl_config(const struct ia_css_fw_info *fw, spctrl_cfg->sp_entry = 0; spctrl_cfg->program_name = (char *)(program); -#if !defined(HRT_UNSCHED) spctrl_cfg->ddr_data_offset = fw->blob.data_source; spctrl_cfg->dmem_data_addr = fw->blob.data_target; spctrl_cfg->dmem_bss_addr = fw->blob.bss_target; @@ -1621,7 +1560,7 @@ static bool sh_css_setup_spctrl_config(const struct ia_css_fw_info *fw, spctrl_cfg->code_size = fw->blob.size; spctrl_cfg->code = fw->blob.code; spctrl_cfg->sp_entry = fw->info.sp.sp_entry; /* entry function ptr on SP */ -#endif + return true; } void @@ -1708,9 +1647,6 @@ ia_css_init(const struct ia_css_env *env, { enum ia_css_err err; ia_css_spctrl_cfg spctrl_cfg; -#if defined(HAS_BL) - ia_css_blctrl_cfg blctrl_cfg; -#endif void (*flush_func)(struct ia_css_acc_fw *fw); hrt_data select, enable; @@ -1863,26 +1799,6 @@ ia_css_init(const struct ia_css_env *env, return err; } -#if defined(HAS_BL) - if (!sh_css_setup_blctrl_config(&sh_css_bl_fw, BL_PROG_NAME, &blctrl_cfg)) - return IA_CSS_ERR_INTERNAL_ERROR; - err = ia_css_blctrl_load_fw(&blctrl_cfg); - if (err != IA_CSS_SUCCESS) { - IA_CSS_LEAVE_ERR(err); - return err; - } - -#ifdef ISP2401 - err = ia_css_blctrl_add_target_fw_info(&sh_css_sp_fw, IA_CSS_SP0, - get_sp_code_addr(SP0_ID)); - -#endif - if (err != IA_CSS_SUCCESS) { - IA_CSS_LEAVE_ERR(err); - return err; - } -#endif /* HAS_BL */ - #if WITH_PC_MONITORING if (!thread_alive) { thread_alive++; @@ -2003,7 +1919,7 @@ ia_css_enable_isys_event_queue(bool enable) void *sh_css_malloc(size_t size) { - ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE, "sh_css_malloc() enter: size=%d\n",size); + ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE, "sh_css_malloc() enter: size=%zu\n",size); /* FIXME: This first test can probably go away */ if (size == 0) return NULL; @@ -2016,7 +1932,7 @@ void *sh_css_calloc(size_t N, size_t size) { void *p; - ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE, "sh_css_calloc() enter: N=%d, size=%d\n",N,size); + ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE, "sh_css_calloc() enter: N=%zu, size=%zu\n",N,size); /* FIXME: this test can probably go away */ if (size > 0) { @@ -2059,7 +1975,8 @@ map_sp_threads(struct ia_css_stream *stream, bool map) enum ia_css_pipe_id pipe_id; assert(stream != NULL); - IA_CSS_ENTER_PRIVATE("stream = %p, map = %p", stream, map); + IA_CSS_ENTER_PRIVATE("stream = %p, map = %s", + stream, map ? "true" : "false"); if (stream == NULL) { IA_CSS_LEAVE_ERR_PRIVATE(IA_CSS_ERR_INVALID_ARGUMENTS); @@ -2611,15 +2528,8 @@ ia_css_pipe_destroy(struct ia_css_pipe *pipe) break; } -#ifndef ISP2401 - if (pipe->scaler_pp_lut != mmgr_NULL) { - hmm_free(pipe->scaler_pp_lut); - pipe->scaler_pp_lut = mmgr_NULL; - } -#else sh_css_params_free_gdc_lut(pipe->scaler_pp_lut); pipe->scaler_pp_lut = mmgr_NULL; -#endif my_css.active_pipes[ia_css_pipe_get_pipe_num(pipe)] = NULL; sh_css_pipe_free_shading_table(pipe); @@ -2666,9 +2576,6 @@ ia_css_uninit(void) } ia_css_spctrl_unload_fw(SP0_ID); sh_css_sp_set_sp_running(false); -#if defined(HAS_BL) - ia_css_blctrl_unload_fw(); -#endif #if defined(USE_INPUT_SYSTEM_VERSION_2) || defined(USE_INPUT_SYSTEM_VERSION_2401) /* check and free any remaining mipi frames */ free_mipi_frames(NULL); @@ -2683,23 +2590,6 @@ ia_css_uninit(void) ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE, "ia_css_uninit() leave: return_void\n"); } -#ifndef ISP2401 -/* Deprecated, this is an HRT backend function (memory_access.h) */ -static void -sh_css_mmu_set_page_table_base_index(hrt_data base_index) -{ - int i; - ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE_PRIVATE, "sh_css_mmu_set_page_table_base_index() enter: base_index=0x%08x\n",base_index); - my_css.page_table_base_index = base_index; - for (i = 0; i < (int)N_MMU_ID; i++) { - mmu_ID_t mmu_id = (mmu_ID_t)i; - mmu_set_page_table_base_index(mmu_id, base_index); - mmu_invalidate_cache(mmu_id); - } - ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE_PRIVATE, "sh_css_mmu_set_page_table_base_index() leave: return_void\n"); -} - -#endif #if defined(HAS_IRQ_MAP_VERSION_2) enum ia_css_err ia_css_irq_translate( unsigned int *irq_infos) @@ -2766,7 +2656,7 @@ enum ia_css_err ia_css_irq_translate( *irq_infos = infos; ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE, "ia_css_irq_translate() " - "leave: irq_infos=%p\n", infos); + "leave: irq_infos=%u\n", infos); return IA_CSS_SUCCESS; } @@ -3004,11 +2894,8 @@ load_preview_binaries(struct ia_css_pipe *pipe) #endif /* preview only have 1 output pin now */ struct ia_css_frame_info *pipe_out_info = &pipe->output_info[0]; -#ifdef ISP2401 struct ia_css_preview_settings *mycs = &pipe->pipe_settings.preview; -#endif - IA_CSS_ENTER_PRIVATE(""); assert(pipe != NULL); assert(pipe->stream != NULL); @@ -3020,11 +2907,7 @@ load_preview_binaries(struct ia_css_pipe *pipe) sensor = pipe->stream->config.mode == IA_CSS_INPUT_MODE_SENSOR; #endif -#ifndef ISP2401 - if (pipe->pipe_settings.preview.preview_binary.info) -#else if (mycs->preview_binary.info) -#endif return IA_CSS_SUCCESS; err = ia_css_util_check_input(&pipe->stream->config, false, false); @@ -3077,12 +2960,7 @@ load_preview_binaries(struct ia_css_pipe *pipe) &prev_vf_info); if (err != IA_CSS_SUCCESS) return err; - err = ia_css_binary_find(&preview_descr, -#ifndef ISP2401 - &pipe->pipe_settings.preview.preview_binary); -#else - &mycs->preview_binary); -#endif + err = ia_css_binary_find(&preview_descr, &mycs->preview_binary); if (err != IA_CSS_SUCCESS) return err; @@ -3098,24 +2976,15 @@ load_preview_binaries(struct ia_css_pipe *pipe) #endif /* The vf_pp binary is needed when (further) YUV downscaling is required */ -#ifndef ISP2401 - need_vf_pp |= pipe->pipe_settings.preview.preview_binary.out_frame_info[0].res.width != pipe_out_info->res.width; - need_vf_pp |= pipe->pipe_settings.preview.preview_binary.out_frame_info[0].res.height != pipe_out_info->res.height; -#else need_vf_pp |= mycs->preview_binary.out_frame_info[0].res.width != pipe_out_info->res.width; need_vf_pp |= mycs->preview_binary.out_frame_info[0].res.height != pipe_out_info->res.height; -#endif /* When vf_pp is needed, then the output format of the selected * preview binary must be yuv_line. If this is not the case, * then the preview binary selection is done again. */ if (need_vf_pp && -#ifndef ISP2401 - (pipe->pipe_settings.preview.preview_binary.out_frame_info[0].format != IA_CSS_FRAME_FORMAT_YUV_LINE)) { -#else (mycs->preview_binary.out_frame_info[0].format != IA_CSS_FRAME_FORMAT_YUV_LINE)) { -#endif /* Preview step 2 */ if (pipe->vf_yuv_ds_input_info.res.width) @@ -3136,11 +3005,7 @@ load_preview_binaries(struct ia_css_pipe *pipe) if (err != IA_CSS_SUCCESS) return err; err = ia_css_binary_find(&preview_descr, -#ifndef ISP2401 - &pipe->pipe_settings.preview.preview_binary); -#else &mycs->preview_binary); -#endif if (err != IA_CSS_SUCCESS) return err; } @@ -3150,18 +3015,10 @@ load_preview_binaries(struct ia_css_pipe *pipe) /* Viewfinder post-processing */ ia_css_pipe_get_vfpp_binarydesc(pipe, &vf_pp_descr, -#ifndef ISP2401 - &pipe->pipe_settings.preview.preview_binary.out_frame_info[0], -#else &mycs->preview_binary.out_frame_info[0], -#endif pipe_out_info); err = ia_css_binary_find(&vf_pp_descr, -#ifndef ISP2401 - &pipe->pipe_settings.preview.vf_pp_binary); -#else &mycs->vf_pp_binary); -#endif if (err != IA_CSS_SUCCESS) return err; } @@ -3187,13 +3044,8 @@ load_preview_binaries(struct ia_css_pipe *pipe) /* Copy */ if (need_isp_copy_binary) { err = load_copy_binary(pipe, -#ifndef ISP2401 - &pipe->pipe_settings.preview.copy_binary, - &pipe->pipe_settings.preview.preview_binary); -#else &mycs->copy_binary, &mycs->preview_binary); -#endif if (err != IA_CSS_SUCCESS) return err; } @@ -4499,22 +4351,10 @@ ia_css_pipe_enqueue_buffer(struct ia_css_pipe *pipe, } if (return_err == IA_CSS_SUCCESS) { -#ifndef ISP2401 - bool found_record = false; - found_record = sh_css_hmm_buffer_record_acquire( -#else - struct sh_css_hmm_buffer_record *hmm_buffer_record = NULL; - - hmm_buffer_record = sh_css_hmm_buffer_record_acquire( -#endif - h_vbuf, buf_type, - HOST_ADDRESS(ddr_buffer.kernel_ptr)); -#ifndef ISP2401 - if (found_record == true) { -#else - if (hmm_buffer_record) { -#endif - IA_CSS_LOG("send vbuf=0x%x", h_vbuf); + if (sh_css_hmm_buffer_record_acquire( + h_vbuf, buf_type, + HOST_ADDRESS(ddr_buffer.kernel_ptr))) { + IA_CSS_LOG("send vbuf=%p", h_vbuf); } else { return_err = IA_CSS_ERR_INTERNAL_ERROR; IA_CSS_ERROR("hmm_buffer_record[]: no available slots\n"); @@ -4624,7 +4464,7 @@ ia_css_pipe_dequeue_buffer(struct ia_css_pipe *pipe, ia_css_rmgr_rel_vbuf(hmm_buffer_pool, &hmm_buffer_record->h_vbuf); sh_css_hmm_buffer_record_reset(hmm_buffer_record); } else { - IA_CSS_ERROR("hmm_buffer_record not found (0x%p) buf_type(%d)", + IA_CSS_ERROR("hmm_buffer_record not found (0x%u) buf_type(%d)", ddr_buffer_addr, buf_type); IA_CSS_LEAVE_ERR(IA_CSS_ERR_INTERNAL_ERROR); return IA_CSS_ERR_INTERNAL_ERROR; @@ -4640,8 +4480,8 @@ ia_css_pipe_dequeue_buffer(struct ia_css_pipe *pipe, if ((ddr_buffer.kernel_ptr == 0) || (kernel_ptr != HOST_ADDRESS(ddr_buffer.kernel_ptr))) { IA_CSS_ERROR("kernel_ptr invalid"); - IA_CSS_ERROR("expected: (0x%p)", kernel_ptr); - IA_CSS_ERROR("actual: (0x%p)", HOST_ADDRESS(ddr_buffer.kernel_ptr)); + IA_CSS_ERROR("expected: (0x%llx)", (u64)kernel_ptr); + IA_CSS_ERROR("actual: (0x%llx)", (u64)HOST_ADDRESS(ddr_buffer.kernel_ptr)); IA_CSS_ERROR("buf_type: %d\n", buf_type); IA_CSS_LEAVE_ERR(IA_CSS_ERR_INTERNAL_ERROR); return IA_CSS_ERR_INTERNAL_ERROR; @@ -6316,9 +6156,6 @@ static enum ia_css_err load_primary_binaries( #else *pipe_vf_out_info; #endif -#if defined(HAS_RES_MGR) - struct ia_css_frame_info bds_out_info; -#endif enum ia_css_err err = IA_CSS_SUCCESS; struct ia_css_capture_settings *mycs; unsigned int i; @@ -6440,10 +6277,6 @@ static enum ia_css_err load_primary_binaries( &cas_scaler_descr.out_info[i], &cas_scaler_descr.internal_out_info[i], &cas_scaler_descr.vf_info[i]); -#if defined(HAS_RES_MGR) - bds_out_info.res = pipe->config.bayer_ds_out_res; - yuv_scaler_descr.bds_out_info = &bds_out_info; -#endif err = ia_css_binary_find(&yuv_scaler_descr, &mycs->yuv_scaler_binary[i]); if (err != IA_CSS_SUCCESS) { @@ -6494,10 +6327,6 @@ static enum ia_css_err load_primary_binaries( &capture_pp_descr, &prim_out_info, #endif &capt_pp_out_info, &vf_info); -#if defined(HAS_RES_MGR) - bds_out_info.res = pipe->config.bayer_ds_out_res; - capture_pp_descr.bds_out_info = &bds_out_info; -#endif err = ia_css_binary_find(&capture_pp_descr, &mycs->capture_pp_binary); if (err != IA_CSS_SUCCESS) { @@ -6533,10 +6362,6 @@ static enum ia_css_err load_primary_binaries( if (pipe->enable_viewfinder[IA_CSS_PIPE_OUTPUT_STAGE_0] && (i == mycs->num_primary_stage - 1)) local_vf_info = &vf_info; ia_css_pipe_get_primary_binarydesc(pipe, &prim_descr[i], &prim_in_info, &prim_out_info, local_vf_info, i); -#if defined(HAS_RES_MGR) - bds_out_info.res = pipe->config.bayer_ds_out_res; - prim_descr[i].bds_out_info = &bds_out_info; -#endif err = ia_css_binary_find(&prim_descr[i], &mycs->primary_binary[i]); if (err != IA_CSS_SUCCESS) { IA_CSS_LEAVE_ERR_PRIVATE(err); @@ -6570,10 +6395,6 @@ static enum ia_css_err load_primary_binaries( ia_css_pipe_get_vfpp_binarydesc(pipe, &vf_pp_descr, vf_pp_in_info, pipe_vf_out_info); -#if defined(HAS_RES_MGR) - bds_out_info.res = pipe->config.bayer_ds_out_res; - vf_pp_descr.bds_out_info = &bds_out_info; -#endif err = ia_css_binary_find(&vf_pp_descr, &mycs->vf_pp_binary); if (err != IA_CSS_SUCCESS) { IA_CSS_LEAVE_ERR_PRIVATE(err); @@ -6621,7 +6442,7 @@ allocate_delay_frames(struct ia_css_pipe *pipe) IA_CSS_ENTER_PRIVATE(""); if (pipe == NULL) { - IA_CSS_ERROR("Invalid args - pipe %x", pipe); + IA_CSS_ERROR("Invalid args - pipe %p", pipe); return IA_CSS_ERR_INVALID_ARGUMENTS; } @@ -8628,9 +8449,7 @@ remove_firmware(struct ia_css_fw_info **l, struct ia_css_fw_info *firmware) return; /* removing single and multiple firmware is handled in acc_unload_extension() */ } -#if !defined(HRT_UNSCHED) -static enum ia_css_err -upload_isp_code(struct ia_css_fw_info *firmware) +static enum ia_css_err upload_isp_code(struct ia_css_fw_info *firmware) { hrt_vaddress binary; @@ -8658,12 +8477,10 @@ upload_isp_code(struct ia_css_fw_info *firmware) return IA_CSS_ERR_CANNOT_ALLOCATE_MEMORY; return IA_CSS_SUCCESS; } -#endif static enum ia_css_err acc_load_extension(struct ia_css_fw_info *firmware) { -#if !defined(HRT_UNSCHED) enum ia_css_err err; struct ia_css_fw_info *hd = firmware; while (hd){ @@ -8672,7 +8489,6 @@ acc_load_extension(struct ia_css_fw_info *firmware) return err; hd = hd->next; } -#endif if (firmware == NULL) return IA_CSS_ERR_INVALID_ARGUMENTS; @@ -9879,9 +9695,6 @@ ia_css_stream_create(const struct ia_css_stream_config *stream_config, /* take over effective info */ effective_res = curr_pipe->config.input_effective_res; -#endif - -#ifndef ISP2401 err = ia_css_util_check_res( effective_res.width, effective_res.height); @@ -9902,13 +9715,6 @@ ia_css_stream_create(const struct ia_css_stream_config *stream_config, if (err != IA_CSS_SUCCESS) goto ERR; -#if defined(HAS_RES_MGR) - /* update acc configuration - striping info is ready */ - err = ia_css_update_cfg_stripe_info(curr_pipe); - if (err != IA_CSS_SUCCESS) - goto ERR; -#endif - /* handle each pipe */ pipe_info = &curr_pipe->info; for (j = 0; j < IA_CSS_PIPE_MAX_OUTPUT_STAGE; j++) { @@ -10587,39 +10393,6 @@ ia_css_pipe_get_isp_pipe_version(const struct ia_css_pipe *pipe) return (unsigned int)pipe->config.isp_pipe_version; } -#if defined(HAS_BL) -#define BL_START_TIMEOUT_US 30000000 -static enum ia_css_err -ia_css_start_bl(void) -{ - enum ia_css_err err = IA_CSS_SUCCESS; - unsigned long timeout; - - IA_CSS_ENTER(""); - sh_css_start_bl(); - /* waiting for the Bootloader to complete execution */ - timeout = BL_START_TIMEOUT_US; - while((ia_css_blctrl_get_state() == BOOTLOADER_BUSY) && timeout) { - timeout--; - hrt_sleep(); - } - ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE, - "Bootloader state %d\n", ia_css_blctrl_get_state()); - if (timeout == 0) { - ia_css_debug_dtrace(IA_CSS_DEBUG_ERROR, - "Bootloader Execution Timeout\n"); - err = IA_CSS_ERR_INTERNAL_ERROR; - } - if (ia_css_blctrl_get_state() != BOOTLOADER_OK) { - ia_css_debug_dtrace(IA_CSS_DEBUG_ERROR, - "Bootloader Execution Failed\n"); - err = IA_CSS_ERR_INTERNAL_ERROR; - } - IA_CSS_LEAVE_ERR(err); - return err; -} -#endif - #define SP_START_TIMEOUT_US 30000000 enum ia_css_err @@ -10629,15 +10402,6 @@ ia_css_start_sp(void) enum ia_css_err err = IA_CSS_SUCCESS; IA_CSS_ENTER(""); -#if defined(HAS_BL) - /* Starting bootloader before Sp0 and Sp1 - * and not exposing CSS API */ - err = ia_css_start_bl(); - if (err != IA_CSS_SUCCESS) { - IA_CSS_LEAVE("Bootloader fails"); - return err; - } -#endif sh_css_sp_start_isp(); /* waiting for the SP is completely started */ @@ -11291,23 +11055,14 @@ sh_css_hmm_buffer_record_reset(struct sh_css_hmm_buffer_record *buffer_record) buffer_record->kernel_ptr = 0; } -#ifndef ISP2401 -static bool -sh_css_hmm_buffer_record_acquire(struct ia_css_rmgr_vbuf_handle *h_vbuf, -#else static struct sh_css_hmm_buffer_record *sh_css_hmm_buffer_record_acquire(struct ia_css_rmgr_vbuf_handle *h_vbuf, -#endif enum ia_css_buffer_type type, hrt_address kernel_ptr) { int i; struct sh_css_hmm_buffer_record *buffer_record = NULL; -#ifndef ISP2401 - bool found_record = false; -#else struct sh_css_hmm_buffer_record *out_buffer_record = NULL; -#endif assert(h_vbuf != NULL); assert((type > IA_CSS_BUFFER_TYPE_INVALID) && (type < IA_CSS_NUM_DYNAMIC_BUFFER_TYPE)); @@ -11320,21 +11075,13 @@ static struct sh_css_hmm_buffer_record buffer_record->type = type; buffer_record->h_vbuf = h_vbuf; buffer_record->kernel_ptr = kernel_ptr; -#ifndef ISP2401 - found_record = true; -#else out_buffer_record = buffer_record; -#endif break; } buffer_record++; } -#ifndef ISP2401 - return found_record; -#else return out_buffer_record; -#endif } static struct sh_css_hmm_buffer_record diff --git a/drivers/staging/media/atomisp/pci/atomisp2/css2400/sh_css_firmware.c b/drivers/staging/media/atomisp/pci/atomisp2/css2400/sh_css_firmware.c index 34cc56f0b471..eecd8cf71951 100644 --- a/drivers/staging/media/atomisp/pci/atomisp2/css2400/sh_css_firmware.c +++ b/drivers/staging/media/atomisp/pci/atomisp2/css2400/sh_css_firmware.c @@ -63,9 +63,6 @@ static const char *release_version = STR(irci_ecr-master_20150911_0724); static char FW_rel_ver_name[MAX_FW_REL_VER_NAME] = "---"; struct ia_css_fw_info sh_css_sp_fw; -#if defined(HAS_BL) -struct ia_css_fw_info sh_css_bl_fw; -#endif /* HAS_BL */ struct ia_css_blob_descr *sh_css_blob_info; /* Only ISP blob info (no SP) */ unsigned sh_css_num_binaries; /* This includes 1 SP binary */ @@ -95,12 +92,7 @@ setup_binary(struct ia_css_fw_info *fw, const char *fw_data, struct ia_css_fw_in *sh_css_fw = *fw; -#if defined(HRT_UNSCHED) - sh_css_fw->blob.code = vmalloc(1); -#else sh_css_fw->blob.code = vmalloc(fw->blob.size); -#endif - if (sh_css_fw->blob.code == NULL) return IA_CSS_ERR_CANNOT_ALLOCATE_MEMORY; @@ -137,12 +129,7 @@ sh_css_load_blob_info(const char *fw, const struct ia_css_fw_info *bi, struct ia bd->blob = blob; bd->header = *bi; - if ((bi->type == ia_css_isp_firmware) || (bi->type == ia_css_sp_firmware) -#if defined(HAS_BL) - || (bi->type == ia_css_bootloader_firmware) -#endif /* HAS_BL */ - ) - { + if (bi->type == ia_css_isp_firmware || bi->type == ia_css_sp_firmware) { char *namebuffer; int namelength = (int)strlen(name); @@ -242,9 +229,9 @@ sh_css_load_firmware(const char *fw_data, sh_css_num_binaries = file_header->binary_nr; /* Only allocate memory for ISP blob info */ - if (sh_css_num_binaries > (NUM_OF_SPS + NUM_OF_BLS)) { + if (sh_css_num_binaries > NUM_OF_SPS) { sh_css_blob_info = kmalloc( - (sh_css_num_binaries - (NUM_OF_SPS + NUM_OF_BLS)) * + (sh_css_num_binaries - NUM_OF_SPS) * sizeof(*sh_css_blob_info), GFP_KERNEL); if (sh_css_blob_info == NULL) return IA_CSS_ERR_CANNOT_ALLOCATE_MEMORY; @@ -279,25 +266,16 @@ sh_css_load_firmware(const char *fw_data, err = setup_binary(bi, fw_data, &sh_css_sp_fw, i); if (err != IA_CSS_SUCCESS) return err; -#if defined(HAS_BL) - } else if (bi->type == ia_css_bootloader_firmware) { - if (i != BOOTLOADER_FIRMWARE) - return IA_CSS_ERR_INTERNAL_ERROR; - err = setup_binary(bi, fw_data, &sh_css_bl_fw, i); - if (err != IA_CSS_SUCCESS) - return err; - IA_CSS_LOG("Bootloader binary recognized\n"); -#endif } else { - /* All subsequent binaries (including bootloaders) (i>NUM_OF_SPS+NUM_OF_BLS) are ISP firmware */ - if (i < (NUM_OF_SPS + NUM_OF_BLS)) + /* All subsequent binaries (including bootloaders) (i>NUM_OF_SPS) are ISP firmware */ + if (i < NUM_OF_SPS) return IA_CSS_ERR_INTERNAL_ERROR; if (bi->type != ia_css_isp_firmware) return IA_CSS_ERR_INTERNAL_ERROR; if (sh_css_blob_info == NULL) /* cannot happen but KW does not see this */ return IA_CSS_ERR_INTERNAL_ERROR; - sh_css_blob_info[i-(NUM_OF_SPS + NUM_OF_BLS)] = bd; + sh_css_blob_info[i - NUM_OF_SPS] = bd; } } diff --git a/drivers/staging/media/atomisp/pci/atomisp2/css2400/sh_css_internal.h b/drivers/staging/media/atomisp/pci/atomisp2/css2400/sh_css_internal.h index e2b6f06ed099..5b2b78f96dc5 100644 --- a/drivers/staging/media/atomisp/pci/atomisp2/css2400/sh_css_internal.h +++ b/drivers/staging/media/atomisp/pci/atomisp2/css2400/sh_css_internal.h @@ -154,18 +154,11 @@ /* Number of SP's */ #define NUM_OF_SPS 1 -#if defined(HAS_BL) -#define NUM_OF_BLS 1 -#else #define NUM_OF_BLS 0 -#endif /* Enum for order of Binaries */ enum sh_css_order_binaries { SP_FIRMWARE = 0, -#if defined(HAS_BL) - BOOTLOADER_FIRMWARE, -#endif ISP_FIRMWARE }; diff --git a/drivers/staging/media/atomisp/pci/atomisp2/css2400/sh_css_irq.c b/drivers/staging/media/atomisp/pci/atomisp2/css2400/sh_css_irq.c deleted file mode 100644 index 37e954aea36f..000000000000 --- a/drivers/staging/media/atomisp/pci/atomisp2/css2400/sh_css_irq.c +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Support for Intel Camera Imaging ISP subsystem. - * Copyright (c) 2015, Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope 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. - */ - -/* This file will contain the code to implement the functions declared in ia_css_irq.h - and associated helper functions */ diff --git a/drivers/staging/media/atomisp/pci/atomisp2/css2400/sh_css_mipi.c b/drivers/staging/media/atomisp/pci/atomisp2/css2400/sh_css_mipi.c index 7e3893c6c08a..36aaa3019a15 100644 --- a/drivers/staging/media/atomisp/pci/atomisp2/css2400/sh_css_mipi.c +++ b/drivers/staging/media/atomisp/pci/atomisp2/css2400/sh_css_mipi.c @@ -681,7 +681,7 @@ send_mipi_frames(struct ia_css_pipe *pipe) unsigned int port = 0; #endif - IA_CSS_ENTER_PRIVATE("pipe=%d", pipe); + IA_CSS_ENTER_PRIVATE("pipe=%p", pipe); assert(pipe != NULL); assert(pipe->stream != NULL); diff --git a/drivers/staging/media/atomisp/pci/atomisp2/css2400/sh_css_mmu.c b/drivers/staging/media/atomisp/pci/atomisp2/css2400/sh_css_mmu.c index 6de8472f1b07..237e38b2f0c1 100644 --- a/drivers/staging/media/atomisp/pci/atomisp2/css2400/sh_css_mmu.c +++ b/drivers/staging/media/atomisp/pci/atomisp2/css2400/sh_css_mmu.c @@ -13,16 +13,12 @@ */ #include "ia_css_mmu.h" -#ifdef ISP2401 #include "ia_css_mmu_private.h" -#endif #include <ia_css_debug.h> #include "sh_css_sp.h" #include "sh_css_firmware.h" #include "sp.h" -#ifdef ISP2401 #include "mmu_device.h" -#endif void ia_css_mmu_invalidate_cache(void) @@ -44,7 +40,6 @@ ia_css_mmu_invalidate_cache(void) } ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE, "ia_css_mmu_invalidate_cache() leave\n"); } -#ifdef ISP2401 /* Deprecated, this is an HRT backend function (memory_access.h) */ void @@ -59,4 +54,3 @@ sh_css_mmu_set_page_table_base_index(hrt_data base_index) } IA_CSS_LEAVE_PRIVATE(""); } -#endif diff --git a/drivers/staging/media/atomisp/pci/atomisp2/css2400/sh_css_params.c b/drivers/staging/media/atomisp/pci/atomisp2/css2400/sh_css_params.c index 561f4a7236f7..48224370b8bf 100644 --- a/drivers/staging/media/atomisp/pci/atomisp2/css2400/sh_css_params.c +++ b/drivers/staging/media/atomisp/pci/atomisp2/css2400/sh_css_params.c @@ -3356,15 +3356,8 @@ enum ia_css_err ia_css_pipe_set_bci_scaler_lut(struct ia_css_pipe *pipe, } /* Free any existing tables. */ -#ifndef ISP2401 - if (pipe->scaler_pp_lut != mmgr_NULL) { - hmm_free(pipe->scaler_pp_lut); - pipe->scaler_pp_lut = mmgr_NULL; - } -#else sh_css_params_free_gdc_lut(pipe->scaler_pp_lut); pipe->scaler_pp_lut = mmgr_NULL; -#endif #ifndef ISP2401 if (store) { @@ -3375,7 +3368,7 @@ enum ia_css_err ia_css_pipe_set_bci_scaler_lut(struct ia_css_pipe *pipe, #endif if (pipe->scaler_pp_lut == mmgr_NULL) { #ifndef ISP2401 - IA_CSS_LEAVE("lut(%p) err=%d", pipe->scaler_pp_lut, err); + IA_CSS_LEAVE("lut(%u) err=%d", pipe->scaler_pp_lut, err); return IA_CSS_ERR_CANNOT_ALLOCATE_MEMORY; #else ia_css_debug_dtrace(IA_CSS_DEBUG_ERROR, @@ -3397,7 +3390,7 @@ enum ia_css_err ia_css_pipe_set_bci_scaler_lut(struct ia_css_pipe *pipe, #endif } - IA_CSS_LEAVE("lut(%p) err=%d", pipe->scaler_pp_lut, err); + IA_CSS_LEAVE("lut(%u) err=%d", pipe->scaler_pp_lut, err); return err; } @@ -3437,7 +3430,7 @@ enum ia_css_err sh_css_params_map_and_store_default_gdc_lut(void) mmgr_store(default_gdc_lut, (int *)interleaved_lut_temp, sizeof(zoom_table)); - IA_CSS_LEAVE_PRIVATE("lut(%p) err=%d", default_gdc_lut, err); + IA_CSS_LEAVE_PRIVATE("lut(%u) err=%d", default_gdc_lut, err); return err; } @@ -3445,15 +3438,8 @@ void sh_css_params_free_default_gdc_lut(void) { IA_CSS_ENTER_PRIVATE("void"); -#ifndef ISP2401 - if (default_gdc_lut != mmgr_NULL) { - hmm_free(default_gdc_lut); - default_gdc_lut = mmgr_NULL; - } -#else sh_css_params_free_gdc_lut(default_gdc_lut); default_gdc_lut = mmgr_NULL; -#endif IA_CSS_LEAVE_PRIVATE("void"); @@ -3859,7 +3845,7 @@ sh_css_param_update_isp_params(struct ia_css_pipe *curr_pipe, /* When API change is implemented making good distinction between * stream config and pipe config this skipping code can be moved out of the #ifdef */ if (pipe_in && (pipe != pipe_in)) { - IA_CSS_LOG("skipping pipe %x", pipe); + IA_CSS_LOG("skipping pipe %p", pipe); continue; } @@ -4590,7 +4576,7 @@ free_ia_css_isp_parameter_set_info( unsigned int i; hrt_vaddress *addrs = (hrt_vaddress *)&isp_params_info.mem_map; - IA_CSS_ENTER_PRIVATE("ptr = %p", ptr); + IA_CSS_ENTER_PRIVATE("ptr = %u", ptr); /* sanity check - ptr must be valid */ if (!ia_css_refcount_is_valid(ptr)) { diff --git a/drivers/staging/media/atomisp/pci/atomisp2/hmm/hmm.c b/drivers/staging/media/atomisp/pci/atomisp2/hmm/hmm.c index 57295397da3e..05eeff58a229 100644 --- a/drivers/staging/media/atomisp/pci/atomisp2/hmm/hmm.c +++ b/drivers/staging/media/atomisp/pci/atomisp2/hmm/hmm.c @@ -43,6 +43,7 @@ struct hmm_bo_device bo_device; struct hmm_pool dynamic_pool; struct hmm_pool reserved_pool; static ia_css_ptr dummy_ptr; +static bool hmm_initialized; struct _hmm_mem_stat hmm_mem_stat; /* p: private @@ -186,6 +187,8 @@ int hmm_init(void) if (ret) dev_err(atomisp_dev, "hmm_bo_device_init failed.\n"); + hmm_initialized = true; + /* * As hmm use NULL to indicate invalid ISP virtual address, * and ISP_VM_START is defined to 0 too, so we allocate @@ -193,7 +196,7 @@ int hmm_init(void) * at the beginning, to avoid hmm_alloc return 0 in the * further allocation. */ - dummy_ptr = hmm_alloc(1, HMM_BO_PRIVATE, 0, 0, HMM_UNCACHED); + dummy_ptr = hmm_alloc(1, HMM_BO_PRIVATE, 0, NULL, HMM_UNCACHED); if (!ret) { ret = sysfs_create_group(&atomisp_dev->kobj, @@ -217,6 +220,7 @@ void hmm_cleanup(void) dummy_ptr = 0; hmm_bo_device_exit(&bo_device); + hmm_initialized = false; } ia_css_ptr hmm_alloc(size_t bytes, enum hmm_bo_type type, @@ -229,7 +233,7 @@ ia_css_ptr hmm_alloc(size_t bytes, enum hmm_bo_type type, /* Check if we are initialized. In the ideal world we wouldn't need this but we can tackle it once the driver is a lot cleaner */ - if (!dummy_ptr) + if (!hmm_initialized) hmm_init(); /*Get page number from size*/ pgnr = size_to_pgnr_ceil(bytes); diff --git a/drivers/staging/media/atomisp/pci/atomisp2/hrt/hive_isp_css_mm_hrt.c b/drivers/staging/media/atomisp/pci/atomisp2/hrt/hive_isp_css_mm_hrt.c index 7dff22f59e29..2e78976bb2ac 100644 --- a/drivers/staging/media/atomisp/pci/atomisp2/hrt/hive_isp_css_mm_hrt.c +++ b/drivers/staging/media/atomisp/pci/atomisp2/hrt/hive_isp_css_mm_hrt.c @@ -55,7 +55,7 @@ static ia_css_ptr __hrt_isp_css_mm_alloc(size_t bytes, void *userptr, if (type == HRT_USR_PTR) { if (userptr == NULL) return hmm_alloc(bytes, HMM_BO_PRIVATE, 0, - 0, cached); + NULL, cached); else { if (num_pages < ((__page_align(bytes)) >> PAGE_SHIFT)) dev_err(atomisp_dev, @@ -94,7 +94,7 @@ ia_css_ptr hrt_isp_css_mm_alloc_user_ptr(size_t bytes, void *userptr, ia_css_ptr hrt_isp_css_mm_alloc_cached(size_t bytes) { if (my_userptr == NULL) - return hmm_alloc(bytes, HMM_BO_PRIVATE, 0, 0, + return hmm_alloc(bytes, HMM_BO_PRIVATE, 0, NULL, HMM_CACHED); else { if (my_num_pages < ((__page_align(bytes)) >> PAGE_SHIFT)) diff --git a/drivers/staging/media/atomisp/platform/intel-mid/atomisp_gmin_platform.c b/drivers/staging/media/atomisp/platform/intel-mid/atomisp_gmin_platform.c index 5b4506a71126..edaae93af8f9 100644 --- a/drivers/staging/media/atomisp/platform/intel-mid/atomisp_gmin_platform.c +++ b/drivers/staging/media/atomisp/platform/intel-mid/atomisp_gmin_platform.c @@ -51,7 +51,7 @@ struct gmin_subdev { static struct gmin_subdev gmin_subdevs[MAX_SUBDEVS]; -static enum { PMIC_UNSET = 0, PMIC_REGULATOR, PMIC_AXP, PMIC_TI , +static enum { PMIC_UNSET = 0, PMIC_REGULATOR, PMIC_AXP, PMIC_TI, PMIC_CRYSTALCOVE } pmic_id; /* The atomisp uses type==0 for the end-of-list marker, so leave space. */ @@ -119,7 +119,7 @@ static int af_power_ctrl(struct v4l2_subdev *subdev, int flag) /* * The power here is used for dw9817, * regulator is from rear sensor - */ + */ if (gs->v2p8_vcm_reg) { if (flag) return regulator_enable(gs->v2p8_vcm_reg); @@ -152,13 +152,13 @@ const struct camera_af_platform_data *camera_get_af_platform_data(void) EXPORT_SYMBOL_GPL(camera_get_af_platform_data); int atomisp_register_i2c_module(struct v4l2_subdev *subdev, - struct camera_sensor_platform_data *plat_data, - enum intel_v4l2_subdev_type type) + struct camera_sensor_platform_data *plat_data, + enum intel_v4l2_subdev_type type) { int i; struct i2c_board_info *bi; struct gmin_subdev *gs; - struct i2c_client *client = v4l2_get_subdevdata(subdev); + struct i2c_client *client = v4l2_get_subdevdata(subdev); struct acpi_device *adev; dev_info(&client->dev, "register atomisp i2c module type %d\n", type); @@ -167,12 +167,13 @@ int atomisp_register_i2c_module(struct v4l2_subdev *subdev, * uses ACPI runtime power management for camera devices, but * we don't. Disable it, or else the rails will be needlessly * tickled during suspend/resume. This has caused power and - * performance issues on multiple devices. */ + * performance issues on multiple devices. + */ adev = ACPI_COMPANION(&client->dev); if (adev) adev->power.flags.power_resources = 0; - for (i=0; i < MAX_SUBDEVS; i++) + for (i = 0; i < MAX_SUBDEVS; i++) if (!pdata.subdevs[i].type) break; @@ -182,7 +183,8 @@ int atomisp_register_i2c_module(struct v4l2_subdev *subdev, /* Note subtlety of initialization order: at the point where * this registration API gets called, the platform data * callbacks have probably already been invoked, so the - * gmin_subdev struct is already initialized for us. */ + * gmin_subdev struct is already initialized for us. + */ gs = find_gmin_subdev(subdev); pdata.subdevs[i].type = type; @@ -206,8 +208,10 @@ struct v4l2_subdev *atomisp_gmin_find_subdev(struct i2c_adapter *adapter, struct i2c_board_info *board_info) { int i; - for (i=0; i < MAX_SUBDEVS && pdata.subdevs[i].type; i++) { + + for (i = 0; i < MAX_SUBDEVS && pdata.subdevs[i].type; i++) { struct intel_v4l2_subdev_table *sd = &pdata.subdevs[i]; + if (sd->v4l2_subdev.i2c_adapter_id == adapter->nr && sd->v4l2_subdev.board_info.addr == board_info->addr) return sd->subdev; @@ -261,7 +265,8 @@ static const struct gmin_cfg_var ffrd8_vars[] = { }; /* Cribbed from MCG defaults in the mt9m114 driver, not actually verified - * vs. T100 hardware */ + * vs. T100 hardware + */ static const struct gmin_cfg_var t100_vars[] = { { "INT33F0:00_CsiPort", "0" }, { "INT33F0:00_CsiLanes", "1" }, @@ -270,45 +275,45 @@ static const struct gmin_cfg_var t100_vars[] = { }; static const struct gmin_cfg_var mrd7_vars[] = { - {"INT33F8:00_CamType", "1"}, - {"INT33F8:00_CsiPort", "1"}, - {"INT33F8:00_CsiLanes","2"}, - {"INT33F8:00_CsiFmt","13"}, - {"INT33F8:00_CsiBayer", "0"}, - {"INT33F8:00_CamClk", "0"}, - {"INT33F9:00_CamType", "1"}, - {"INT33F9:00_CsiPort", "0"}, - {"INT33F9:00_CsiLanes","1"}, - {"INT33F9:00_CsiFmt","13"}, - {"INT33F9:00_CsiBayer", "0"}, - {"INT33F9:00_CamClk", "1"}, - {}, + {"INT33F8:00_CamType", "1"}, + {"INT33F8:00_CsiPort", "1"}, + {"INT33F8:00_CsiLanes", "2"}, + {"INT33F8:00_CsiFmt", "13"}, + {"INT33F8:00_CsiBayer", "0"}, + {"INT33F8:00_CamClk", "0"}, + {"INT33F9:00_CamType", "1"}, + {"INT33F9:00_CsiPort", "0"}, + {"INT33F9:00_CsiLanes", "1"}, + {"INT33F9:00_CsiFmt", "13"}, + {"INT33F9:00_CsiBayer", "0"}, + {"INT33F9:00_CamClk", "1"}, + {}, }; static const struct gmin_cfg_var ecs7_vars[] = { - {"INT33BE:00_CsiPort", "1"}, - {"INT33BE:00_CsiLanes","2"}, - {"INT33BE:00_CsiFmt","13"}, - {"INT33BE:00_CsiBayer", "2"}, - {"INT33BE:00_CamClk", "0"}, - {"INT33F0:00_CsiPort", "0"}, - {"INT33F0:00_CsiLanes","1"}, - {"INT33F0:00_CsiFmt","13"}, - {"INT33F0:00_CsiBayer", "0"}, - {"INT33F0:00_CamClk", "1"}, - {"gmin_V2P8GPIO","402"}, - {}, + {"INT33BE:00_CsiPort", "1"}, + {"INT33BE:00_CsiLanes", "2"}, + {"INT33BE:00_CsiFmt", "13"}, + {"INT33BE:00_CsiBayer", "2"}, + {"INT33BE:00_CamClk", "0"}, + {"INT33F0:00_CsiPort", "0"}, + {"INT33F0:00_CsiLanes", "1"}, + {"INT33F0:00_CsiFmt", "13"}, + {"INT33F0:00_CsiBayer", "0"}, + {"INT33F0:00_CamClk", "1"}, + {"gmin_V2P8GPIO", "402"}, + {}, }; static const struct gmin_cfg_var i8880_vars[] = { - {"XXOV2680:00_CsiPort", "1"}, - {"XXOV2680:00_CsiLanes","1"}, - {"XXOV2680:00_CamClk","0"}, - {"XXGC0310:00_CsiPort", "0"}, - {"XXGC0310:00_CsiLanes", "1"}, - {"XXGC0310:00_CamClk", "1"}, - {}, + {"XXOV2680:00_CsiPort", "1"}, + {"XXOV2680:00_CsiLanes", "1"}, + {"XXOV2680:00_CamClk", "0"}, + {"XXGC0310:00_CsiPort", "0"}, + {"XXGC0310:00_CsiLanes", "1"}, + {"XXGC0310:00_CamClk", "1"}, + {}, }; static const struct { @@ -317,9 +322,9 @@ static const struct { } hard_vars[] = { { "BYT-T FFD8", ffrd8_vars }, { "T100TA", t100_vars }, - { "MRD7", mrd7_vars }, - { "ST70408", ecs7_vars }, - { "VTA0803", i8880_vars }, + { "MRD7", mrd7_vars }, + { "ST70408", ecs7_vars }, + { "VTA0803", i8880_vars }, }; @@ -343,19 +348,17 @@ static struct gmin_subdev *gmin_subdev_add(struct v4l2_subdev *subdev) { int i, ret; struct device *dev; - struct i2c_client *client = v4l2_get_subdevdata(subdev); - - if (!pmic_id) { + struct i2c_client *client = v4l2_get_subdevdata(subdev); - pmic_id = PMIC_REGULATOR; - } + if (!pmic_id) + pmic_id = PMIC_REGULATOR; if (!client) return NULL; dev = &client->dev; - for (i=0; i < MAX_SUBDEVS && gmin_subdevs[i].subdev; i++) + for (i = 0; i < MAX_SUBDEVS && gmin_subdevs[i].subdev; i++) ; if (i >= MAX_SUBDEVS) return NULL; @@ -401,7 +404,8 @@ static struct gmin_subdev *gmin_subdev_add(struct v4l2_subdev *subdev) * API is broken with the current drivers, returning * "1" for a regulator that will then emit a * "unbalanced disable" WARNing if we try to disable - * it. */ + * it. + */ } return &gmin_subdevs[i]; @@ -410,7 +414,8 @@ static struct gmin_subdev *gmin_subdev_add(struct v4l2_subdev *subdev) static struct gmin_subdev *find_gmin_subdev(struct v4l2_subdev *subdev) { int i; - for (i=0; i < MAX_SUBDEVS; i++) + + for (i = 0; i < MAX_SUBDEVS; i++) if (gmin_subdevs[i].subdev == subdev) return &gmin_subdevs[i]; return gmin_subdev_add(subdev); @@ -419,6 +424,7 @@ static struct gmin_subdev *find_gmin_subdev(struct v4l2_subdev *subdev) static int gmin_gpio0_ctrl(struct v4l2_subdev *subdev, int on) { struct gmin_subdev *gs = find_gmin_subdev(subdev); + if (gs && gs->gpio0) { gpiod_set_value(gs->gpio0, on); return 0; @@ -429,6 +435,7 @@ static int gmin_gpio0_ctrl(struct v4l2_subdev *subdev, int on) static int gmin_gpio1_ctrl(struct v4l2_subdev *subdev, int on) { struct gmin_subdev *gs = find_gmin_subdev(subdev); + if (gs && gs->gpio1) { gpiod_set_value(gs->gpio1, on); return 0; @@ -436,7 +443,7 @@ static int gmin_gpio1_ctrl(struct v4l2_subdev *subdev, int on) return -EINVAL; } -int gmin_v1p2_ctrl(struct v4l2_subdev *subdev, int on) +static int gmin_v1p2_ctrl(struct v4l2_subdev *subdev, int on) { struct gmin_subdev *gs = find_gmin_subdev(subdev); @@ -455,7 +462,8 @@ int gmin_v1p2_ctrl(struct v4l2_subdev *subdev, int on) return -EINVAL; } -int gmin_v1p8_ctrl(struct v4l2_subdev *subdev, int on) + +static int gmin_v1p8_ctrl(struct v4l2_subdev *subdev, int on) { struct gmin_subdev *gs = find_gmin_subdev(subdev); int ret; @@ -481,7 +489,7 @@ int gmin_v1p8_ctrl(struct v4l2_subdev *subdev, int on) gpio_set_value(v1p8_gpio, on); if (gs->v1p8_reg) { - regulator_set_voltage(gs->v1p8_reg, 1800000, 1800000); + regulator_set_voltage(gs->v1p8_reg, 1800000, 1800000); if (on) return regulator_enable(gs->v1p8_reg); else @@ -491,7 +499,7 @@ int gmin_v1p8_ctrl(struct v4l2_subdev *subdev, int on) return -EINVAL; } -int gmin_v2p8_ctrl(struct v4l2_subdev *subdev, int on) +static int gmin_v2p8_ctrl(struct v4l2_subdev *subdev, int on) { struct gmin_subdev *gs = find_gmin_subdev(subdev); int ret; @@ -517,7 +525,7 @@ int gmin_v2p8_ctrl(struct v4l2_subdev *subdev, int on) gpio_set_value(v2p8_gpio, on); if (gs->v2p8_reg) { - regulator_set_voltage(gs->v2p8_reg, 2900000, 2900000); + regulator_set_voltage(gs->v2p8_reg, 2900000, 2900000); if (on) return regulator_enable(gs->v2p8_reg); else @@ -527,10 +535,11 @@ int gmin_v2p8_ctrl(struct v4l2_subdev *subdev, int on) return -EINVAL; } -int gmin_flisclk_ctrl(struct v4l2_subdev *subdev, int on) +static int gmin_flisclk_ctrl(struct v4l2_subdev *subdev, int on) { int ret = 0; struct gmin_subdev *gs = find_gmin_subdev(subdev); + if (on) ret = vlv2_plat_set_clock_freq(gs->clock_num, gs->clock_src); if (ret) @@ -595,6 +604,7 @@ struct camera_sensor_platform_data *gmin_camera_platform_data( enum atomisp_bayer_order csi_bayer) { struct gmin_subdev *gs = find_gmin_subdev(subdev); + gs->csi_fmt = csi_format; gs->csi_bayer = csi_bayer; @@ -617,30 +627,31 @@ EXPORT_SYMBOL_GPL(atomisp_gmin_register_vcm_control); /* Retrieves a device-specific configuration variable. The dev * argument should be a device with an ACPI companion, as all - * configuration is based on firmware ID. */ -int gmin_get_config_var(struct device *dev, const char *var, char *out, size_t *out_len) + * configuration is based on firmware ID. + */ +int gmin_get_config_var(struct device *dev, const char *var, char *out, + size_t *out_len) { char var8[CFG_VAR_NAME_MAX]; efi_char16_t var16[CFG_VAR_NAME_MAX]; struct efivar_entry *ev; - u32 efiattr_dummy; int i, j, ret; - unsigned long efilen; - if (dev && ACPI_COMPANION(dev)) - dev = &ACPI_COMPANION(dev)->dev; + if (dev && ACPI_COMPANION(dev)) + dev = &ACPI_COMPANION(dev)->dev; - if (dev) - ret = snprintf(var8, sizeof(var8), "%s_%s", dev_name(dev), var); - else - ret = snprintf(var8, sizeof(var8), "gmin_%s", var); + if (dev) + ret = snprintf(var8, sizeof(var8), "%s_%s", dev_name(dev), var); + else + ret = snprintf(var8, sizeof(var8), "gmin_%s", var); if (ret < 0 || ret >= sizeof(var8) - 1) return -EINVAL; /* First check a hard-coded list of board-specific variables. * Some device firmwares lack the ability to set EFI variables at - * runtime. */ + * runtime. + */ for (i = 0; i < ARRAY_SIZE(hard_vars); i++) { if (dmi_match(DMI_BOARD_NAME, hard_vars[i].dmi_board_name)) { for (j = 0; hard_vars[i].vars[j].name; j++) { @@ -665,7 +676,8 @@ int gmin_get_config_var(struct device *dev, const char *var, char *out, size_t * } /* Our variable names are ASCII by construction, but EFI names - * are wide chars. Convert and zero-pad. */ + * are wide chars. Convert and zero-pad. + */ memset(var16, 0, sizeof(var16)); for (i = 0; i < sizeof(var8) && var8[i]; i++) var16[i] = var8[i]; @@ -678,21 +690,25 @@ int gmin_get_config_var(struct device *dev, const char *var, char *out, size_t * * implementation simply uses VariableName and VendorGuid from * the struct and ignores the rest, but it seems like there * ought to be an "official" efivar_entry registered - * somewhere? */ + * somewhere? + */ ev = kzalloc(sizeof(*ev), GFP_KERNEL); if (!ev) return -ENOMEM; memcpy(&ev->var.VariableName, var16, sizeof(var16)); ev->var.VendorGuid = GMIN_CFG_VAR_EFI_GUID; - - efilen = *out_len; - ret = efivar_entry_get(ev, &efiattr_dummy, &efilen, out); + ev->var.DataSize = *out_len; + + ret = efivar_entry_get(ev, &ev->var.Attributes, + &ev->var.DataSize, ev->var.Data); + if (ret == 0) { + memcpy(out, ev->var.Data, ev->var.DataSize); + *out_len = ev->var.DataSize; + } else if (dev) { + dev_warn(dev, "Failed to find gmin variable %s\n", var8); + } kfree(ev); - *out_len = efilen; - - if (ret) - dev_warn(dev, "Failed to find gmin variable %s\n", var8); return ret; } @@ -718,38 +734,39 @@ EXPORT_SYMBOL_GPL(gmin_get_var_int); int camera_sensor_csi(struct v4l2_subdev *sd, u32 port, u32 lanes, u32 format, u32 bayer_order, int flag) { - struct i2c_client *client = v4l2_get_subdevdata(sd); - struct camera_mipi_info *csi = NULL; - - if (flag) { - csi = kzalloc(sizeof(*csi), GFP_KERNEL); - if (!csi) { - dev_err(&client->dev, "out of memory\n"); - return -ENOMEM; - } - csi->port = port; - csi->num_lanes = lanes; - csi->input_format = format; - csi->raw_bayer_order = bayer_order; - v4l2_set_subdev_hostdata(sd, (void *)csi); - csi->metadata_format = ATOMISP_INPUT_FORMAT_EMBEDDED; - csi->metadata_effective_width = NULL; - dev_info(&client->dev, - "camera pdata: port: %d lanes: %d order: %8.8x\n", - port, lanes, bayer_order); - } else { - csi = v4l2_get_subdev_hostdata(sd); - kfree(csi); - } - - return 0; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct camera_mipi_info *csi = NULL; + + if (flag) { + csi = kzalloc(sizeof(*csi), GFP_KERNEL); + if (!csi) { + dev_err(&client->dev, "out of memory\n"); + return -ENOMEM; + } + csi->port = port; + csi->num_lanes = lanes; + csi->input_format = format; + csi->raw_bayer_order = bayer_order; + v4l2_set_subdev_hostdata(sd, (void *)csi); + csi->metadata_format = ATOMISP_INPUT_FORMAT_EMBEDDED; + csi->metadata_effective_width = NULL; + dev_info(&client->dev, + "camera pdata: port: %d lanes: %d order: %8.8x\n", + port, lanes, bayer_order); + } else { + csi = v4l2_get_subdev_hostdata(sd); + kfree(csi); + } + + return 0; } EXPORT_SYMBOL_GPL(camera_sensor_csi); /* PCI quirk: The BYT ISP advertises PCI runtime PM but it doesn't * work. Disable so the kernel framework doesn't hang the device * trying. The driver itself does direct calls to the PUNIT to manage - * ISP power. */ + * ISP power. + */ static void isp_pm_cap_fixup(struct pci_dev *dev) { dev_info(&dev->dev, "Disabling PCI power management on camera ISP\n"); diff --git a/drivers/staging/media/atomisp/platform/intel-mid/intel_mid_pcihelpers.c b/drivers/staging/media/atomisp/platform/intel-mid/intel_mid_pcihelpers.c index a6c0f5f8c3f8..cd452cc20fea 100644 --- a/drivers/staging/media/atomisp/platform/intel-mid/intel_mid_pcihelpers.c +++ b/drivers/staging/media/atomisp/platform/intel-mid/intel_mid_pcihelpers.c @@ -5,7 +5,8 @@ /* G-Min addition: "platform_is()" lives in intel_mid_pm.h in the MCG * tree, but it's just platform ID info and we don't want to pull in - * the whole SFI-based PM architecture. */ + * the whole SFI-based PM architecture. + */ #define INTEL_ATOM_MRST 0x26 #define INTEL_ATOM_MFLD 0x27 #define INTEL_ATOM_CLV 0x35 @@ -22,7 +23,7 @@ #endif static inline int platform_is(u8 model) { - return (boot_cpu_data.x86_model == model); + return (boot_cpu_data.x86_model == model); } #include "../../include/asm/intel_mid_pcihelpers.h" @@ -32,7 +33,6 @@ static DEFINE_SPINLOCK(msgbus_lock); static struct pci_dev *pci_root; static struct pm_qos_request pm_qos; -int qos; #define DW_I2C_NEED_QOS (platform_is(INTEL_ATOM_BYT)) @@ -136,8 +136,8 @@ u32 intel_mid_msgbus_read32(u8 port, u32 addr) return data; } - EXPORT_SYMBOL(intel_mid_msgbus_read32); + void intel_mid_msgbus_write32(u8 port, u32 addr, u32 data) { unsigned long irq_flags; @@ -171,8 +171,8 @@ EXPORT_SYMBOL(intel_mid_soc_stepping); static bool is_south_complex_device(struct pci_dev *dev) { - unsigned base_class = dev->class >> 16; - unsigned sub_class = (dev->class & SUB_CLASS_MASK) >> 8; + unsigned int base_class = dev->class >> 16; + unsigned int sub_class = (dev->class & SUB_CLASS_MASK) >> 8; /* other than camera, pci bridges and display, * everything else are south complex devices. diff --git a/drivers/staging/media/cxd2099/cxd2099.c b/drivers/staging/media/cxd2099/cxd2099.c index 18186d0fa1a6..370ecb959543 100644 --- a/drivers/staging/media/cxd2099/cxd2099.c +++ b/drivers/staging/media/cxd2099/cxd2099.c @@ -473,7 +473,7 @@ static int slot_shutdown(struct dvb_ca_en50221 *ca, int slot) { struct cxd *ci = ca->data; - dev_info(&ci->i2c->dev, "slot_shutdown\n"); + dev_info(&ci->i2c->dev, "%s\n", __func__); mutex_lock(&ci->lock); write_regm(ci, 0x09, 0x08, 0x08); write_regm(ci, 0x20, 0x80, 0x80); /* Reset CAM Mode */ @@ -564,7 +564,7 @@ static int read_data(struct dvb_ca_en50221 *ca, int slot, u8 *ebuf, int ecount) campoll(ci); mutex_unlock(&ci->lock); - dev_info(&ci->i2c->dev, "read_data\n"); + dev_info(&ci->i2c->dev, "%s\n", __func__); if (!ci->dr) return 0; @@ -584,7 +584,7 @@ static int write_data(struct dvb_ca_en50221 *ca, int slot, u8 *ebuf, int ecount) struct cxd *ci = ca->data; mutex_lock(&ci->lock); - dev_info(&ci->i2c->dev, "write_data %d\n", ecount); + dev_info(&ci->i2c->dev, "%s %d\n", __func__, ecount); write_reg(ci, 0x0d, ecount >> 8); write_reg(ci, 0x0e, ecount & 0xff); write_block(ci, 0x11, ebuf, ecount); diff --git a/drivers/staging/media/imx/Kconfig b/drivers/staging/media/imx/Kconfig new file mode 100644 index 000000000000..7eff50bcea39 --- /dev/null +++ b/drivers/staging/media/imx/Kconfig @@ -0,0 +1,21 @@ +config VIDEO_IMX_MEDIA + tristate "i.MX5/6 V4L2 media core driver" + depends on MEDIA_CONTROLLER && VIDEO_V4L2 && ARCH_MXC && IMX_IPUV3_CORE + select V4L2_FWNODE + ---help--- + Say yes here to enable support for video4linux media controller + driver for the i.MX5/6 SOC. + +if VIDEO_IMX_MEDIA +menu "i.MX5/6 Media Sub devices" + +config VIDEO_IMX_CSI + tristate "i.MX5/6 Camera Sensor Interface driver" + depends on VIDEO_IMX_MEDIA && VIDEO_DEV && I2C + select VIDEOBUF2_DMA_CONTIG + default y + ---help--- + A video4linux camera sensor interface driver for i.MX5/6. + +endmenu +endif diff --git a/drivers/staging/media/imx/Makefile b/drivers/staging/media/imx/Makefile new file mode 100644 index 000000000000..3569625b6305 --- /dev/null +++ b/drivers/staging/media/imx/Makefile @@ -0,0 +1,12 @@ +imx-media-objs := imx-media-dev.o imx-media-internal-sd.o imx-media-of.o +imx-media-common-objs := imx-media-utils.o imx-media-fim.o +imx-media-ic-objs := imx-ic-common.o imx-ic-prp.o imx-ic-prpencvf.o + +obj-$(CONFIG_VIDEO_IMX_MEDIA) += imx-media.o +obj-$(CONFIG_VIDEO_IMX_MEDIA) += imx-media-common.o +obj-$(CONFIG_VIDEO_IMX_MEDIA) += imx-media-capture.o +obj-$(CONFIG_VIDEO_IMX_MEDIA) += imx-media-vdic.o +obj-$(CONFIG_VIDEO_IMX_MEDIA) += imx-media-ic.o + +obj-$(CONFIG_VIDEO_IMX_CSI) += imx-media-csi.o +obj-$(CONFIG_VIDEO_IMX_CSI) += imx6-mipi-csi2.o diff --git a/drivers/staging/media/imx/TODO b/drivers/staging/media/imx/TODO new file mode 100644 index 000000000000..0bee3132b26f --- /dev/null +++ b/drivers/staging/media/imx/TODO @@ -0,0 +1,23 @@ + +- Clean up and move the ov5642 subdev driver to drivers/media/i2c, or + merge support for OV5642 into drivers/media/i2c/ov5640.c, and create + the binding docs for it. + +- The Frame Interval Monitor could be exported to v4l2-core for + general use. + +- At driver load time, the device-tree node that is the original source + (the "sensor"), is parsed to record its media bus configuration, and + this info is required in imx-media-csi.c to setup the CSI. + Laurent Pinchart argues that instead the CSI subdev should call its + neighbor's g_mbus_config op (which should be propagated if necessary) + to get this info. However Hans Verkuil is planning to remove the + g_mbus_config op. For now this driver uses the parsed DT mbus config + method until this issue is resolved. + +- This media driver supports inheriting V4L2 controls to the + video capture devices, from the subdevices in the capture device's + pipeline. The controls for each capture device are updated in the + link_notify callback when the pipeline is modified. It should be + decided whether this feature is useful enough to make it generally + available by exporting to v4l2-core. diff --git a/drivers/staging/media/imx/imx-ic-common.c b/drivers/staging/media/imx/imx-ic-common.c new file mode 100644 index 000000000000..cfdd4900a3be --- /dev/null +++ b/drivers/staging/media/imx/imx-ic-common.c @@ -0,0 +1,113 @@ +/* + * V4L2 Image Converter Subdev for Freescale i.MX5/6 SOC + * + * Copyright (c) 2014-2016 Mentor Graphics Inc. + * + * 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; either version 2 of the License, or + * (at your option) any later version. + */ +#include <linux/module.h> +#include <linux/platform_device.h> +#include <media/v4l2-device.h> +#include <media/v4l2-subdev.h> +#include "imx-media.h" +#include "imx-ic.h" + +#define IC_TASK_PRP IC_NUM_TASKS +#define IC_NUM_OPS (IC_NUM_TASKS + 1) + +static struct imx_ic_ops *ic_ops[IC_NUM_OPS] = { + [IC_TASK_PRP] = &imx_ic_prp_ops, + [IC_TASK_ENCODER] = &imx_ic_prpencvf_ops, + [IC_TASK_VIEWFINDER] = &imx_ic_prpencvf_ops, +}; + +static int imx_ic_probe(struct platform_device *pdev) +{ + struct imx_media_internal_sd_platformdata *pdata; + struct imx_ic_priv *priv; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + platform_set_drvdata(pdev, &priv->sd); + priv->dev = &pdev->dev; + + /* get our ipu_id, grp_id and IC task id */ + pdata = priv->dev->platform_data; + priv->ipu_id = pdata->ipu_id; + switch (pdata->grp_id) { + case IMX_MEDIA_GRP_ID_IC_PRP: + priv->task_id = IC_TASK_PRP; + break; + case IMX_MEDIA_GRP_ID_IC_PRPENC: + priv->task_id = IC_TASK_ENCODER; + break; + case IMX_MEDIA_GRP_ID_IC_PRPVF: + priv->task_id = IC_TASK_VIEWFINDER; + break; + default: + return -EINVAL; + } + + v4l2_subdev_init(&priv->sd, ic_ops[priv->task_id]->subdev_ops); + v4l2_set_subdevdata(&priv->sd, priv); + priv->sd.internal_ops = ic_ops[priv->task_id]->internal_ops; + priv->sd.entity.ops = ic_ops[priv->task_id]->entity_ops; + priv->sd.entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER; + priv->sd.dev = &pdev->dev; + priv->sd.owner = THIS_MODULE; + priv->sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; + priv->sd.grp_id = pdata->grp_id; + strncpy(priv->sd.name, pdata->sd_name, sizeof(priv->sd.name)); + + ret = ic_ops[priv->task_id]->init(priv); + if (ret) + return ret; + + ret = v4l2_async_register_subdev(&priv->sd); + if (ret) + ic_ops[priv->task_id]->remove(priv); + + return ret; +} + +static int imx_ic_remove(struct platform_device *pdev) +{ + struct v4l2_subdev *sd = platform_get_drvdata(pdev); + struct imx_ic_priv *priv = container_of(sd, struct imx_ic_priv, sd); + + v4l2_info(sd, "Removing\n"); + + ic_ops[priv->task_id]->remove(priv); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + + return 0; +} + +static const struct platform_device_id imx_ic_ids[] = { + { .name = "imx-ipuv3-ic" }, + { }, +}; +MODULE_DEVICE_TABLE(platform, imx_ic_ids); + +static struct platform_driver imx_ic_driver = { + .probe = imx_ic_probe, + .remove = imx_ic_remove, + .id_table = imx_ic_ids, + .driver = { + .name = "imx-ipuv3-ic", + }, +}; +module_platform_driver(imx_ic_driver); + +MODULE_DESCRIPTION("i.MX IC subdev driver"); +MODULE_AUTHOR("Steve Longerbeam <steve_longerbeam@mentor.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:imx-ipuv3-ic"); diff --git a/drivers/staging/media/imx/imx-ic-prp.c b/drivers/staging/media/imx/imx-ic-prp.c new file mode 100644 index 000000000000..c2bb5ef2acb4 --- /dev/null +++ b/drivers/staging/media/imx/imx-ic-prp.c @@ -0,0 +1,518 @@ +/* + * V4L2 Capture IC Preprocess Subdev for Freescale i.MX5/6 SOC + * + * This subdevice handles capture of video frames from the CSI or VDIC, + * which are routed directly to the Image Converter preprocess tasks, + * for resizing, colorspace conversion, and rotation. + * + * Copyright (c) 2012-2017 Mentor Graphics Inc. + * + * 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; either version 2 of the License, or + * (at your option) any later version. + */ +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/timer.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-subdev.h> +#include <media/imx.h> +#include "imx-media.h" +#include "imx-ic.h" + +/* + * Min/Max supported width and heights. + */ +#define MIN_W 176 +#define MIN_H 144 +#define MAX_W 4096 +#define MAX_H 4096 +#define W_ALIGN 4 /* multiple of 16 pixels */ +#define H_ALIGN 1 /* multiple of 2 lines */ +#define S_ALIGN 1 /* multiple of 2 */ + +struct prp_priv { + struct imx_media_dev *md; + struct imx_ic_priv *ic_priv; + struct media_pad pad[PRP_NUM_PADS]; + + /* lock to protect all members below */ + struct mutex lock; + + /* IPU units we require */ + struct ipu_soc *ipu; + + struct v4l2_subdev *src_sd; + struct v4l2_subdev *sink_sd_prpenc; + struct v4l2_subdev *sink_sd_prpvf; + + /* the CSI id at link validate */ + int csi_id; + + struct v4l2_mbus_framefmt format_mbus; + struct v4l2_fract frame_interval; + + int stream_count; +}; + +static inline struct prp_priv *sd_to_priv(struct v4l2_subdev *sd) +{ + struct imx_ic_priv *ic_priv = v4l2_get_subdevdata(sd); + + return ic_priv->prp_priv; +} + +static int prp_start(struct prp_priv *priv) +{ + struct imx_ic_priv *ic_priv = priv->ic_priv; + bool src_is_vdic; + + priv->ipu = priv->md->ipu[ic_priv->ipu_id]; + + /* set IC to receive from CSI or VDI depending on source */ + src_is_vdic = !!(priv->src_sd->grp_id & IMX_MEDIA_GRP_ID_VDIC); + + ipu_set_ic_src_mux(priv->ipu, priv->csi_id, src_is_vdic); + + return 0; +} + +static void prp_stop(struct prp_priv *priv) +{ +} + +static struct v4l2_mbus_framefmt * +__prp_get_fmt(struct prp_priv *priv, struct v4l2_subdev_pad_config *cfg, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + struct imx_ic_priv *ic_priv = priv->ic_priv; + + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(&ic_priv->sd, cfg, pad); + else + return &priv->format_mbus; +} + +/* + * V4L2 subdev operations. + */ + +static int prp_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct prp_priv *priv = sd_to_priv(sd); + struct v4l2_mbus_framefmt *infmt; + int ret = 0; + + mutex_lock(&priv->lock); + + switch (code->pad) { + case PRP_SINK_PAD: + ret = imx_media_enum_ipu_format(&code->code, code->index, + CS_SEL_ANY); + break; + case PRP_SRC_PAD_PRPENC: + case PRP_SRC_PAD_PRPVF: + if (code->index != 0) { + ret = -EINVAL; + goto out; + } + infmt = __prp_get_fmt(priv, cfg, PRP_SINK_PAD, code->which); + code->code = infmt->code; + break; + default: + ret = -EINVAL; + } +out: + mutex_unlock(&priv->lock); + return ret; +} + +static int prp_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *sdformat) +{ + struct prp_priv *priv = sd_to_priv(sd); + struct v4l2_mbus_framefmt *fmt; + int ret = 0; + + if (sdformat->pad >= PRP_NUM_PADS) + return -EINVAL; + + mutex_lock(&priv->lock); + + fmt = __prp_get_fmt(priv, cfg, sdformat->pad, sdformat->which); + if (!fmt) { + ret = -EINVAL; + goto out; + } + + sdformat->format = *fmt; +out: + mutex_unlock(&priv->lock); + return ret; +} + +static int prp_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *sdformat) +{ + struct prp_priv *priv = sd_to_priv(sd); + struct v4l2_mbus_framefmt *fmt, *infmt; + const struct imx_media_pixfmt *cc; + int ret = 0; + u32 code; + + if (sdformat->pad >= PRP_NUM_PADS) + return -EINVAL; + + mutex_lock(&priv->lock); + + if (priv->stream_count > 0) { + ret = -EBUSY; + goto out; + } + + infmt = __prp_get_fmt(priv, cfg, PRP_SINK_PAD, sdformat->which); + + switch (sdformat->pad) { + case PRP_SINK_PAD: + v4l_bound_align_image(&sdformat->format.width, MIN_W, MAX_W, + W_ALIGN, &sdformat->format.height, + MIN_H, MAX_H, H_ALIGN, S_ALIGN); + + cc = imx_media_find_ipu_format(sdformat->format.code, + CS_SEL_ANY); + if (!cc) { + imx_media_enum_ipu_format(&code, 0, CS_SEL_ANY); + cc = imx_media_find_ipu_format(code, CS_SEL_ANY); + sdformat->format.code = cc->codes[0]; + } + + imx_media_fill_default_mbus_fields(&sdformat->format, infmt, + true); + break; + case PRP_SRC_PAD_PRPENC: + case PRP_SRC_PAD_PRPVF: + /* Output pads mirror input pad */ + sdformat->format = *infmt; + break; + } + + fmt = __prp_get_fmt(priv, cfg, sdformat->pad, sdformat->which); + *fmt = sdformat->format; +out: + mutex_unlock(&priv->lock); + return ret; +} + +static int prp_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); + struct imx_ic_priv *ic_priv = v4l2_get_subdevdata(sd); + struct prp_priv *priv = ic_priv->prp_priv; + struct v4l2_subdev *remote_sd; + int ret = 0; + + dev_dbg(ic_priv->dev, "link setup %s -> %s", remote->entity->name, + local->entity->name); + + remote_sd = media_entity_to_v4l2_subdev(remote->entity); + + mutex_lock(&priv->lock); + + if (local->flags & MEDIA_PAD_FL_SINK) { + if (flags & MEDIA_LNK_FL_ENABLED) { + if (priv->src_sd) { + ret = -EBUSY; + goto out; + } + if (priv->sink_sd_prpenc && (remote_sd->grp_id & + IMX_MEDIA_GRP_ID_VDIC)) { + ret = -EINVAL; + goto out; + } + priv->src_sd = remote_sd; + } else { + priv->src_sd = NULL; + } + + goto out; + } + + /* this is a source pad */ + if (flags & MEDIA_LNK_FL_ENABLED) { + switch (local->index) { + case PRP_SRC_PAD_PRPENC: + if (priv->sink_sd_prpenc) { + ret = -EBUSY; + goto out; + } + if (priv->src_sd && (priv->src_sd->grp_id & + IMX_MEDIA_GRP_ID_VDIC)) { + ret = -EINVAL; + goto out; + } + priv->sink_sd_prpenc = remote_sd; + break; + case PRP_SRC_PAD_PRPVF: + if (priv->sink_sd_prpvf) { + ret = -EBUSY; + goto out; + } + priv->sink_sd_prpvf = remote_sd; + break; + default: + ret = -EINVAL; + } + } else { + switch (local->index) { + case PRP_SRC_PAD_PRPENC: + priv->sink_sd_prpenc = NULL; + break; + case PRP_SRC_PAD_PRPVF: + priv->sink_sd_prpvf = NULL; + break; + default: + ret = -EINVAL; + } + } + +out: + mutex_unlock(&priv->lock); + return ret; +} + +static int prp_link_validate(struct v4l2_subdev *sd, + struct media_link *link, + struct v4l2_subdev_format *source_fmt, + struct v4l2_subdev_format *sink_fmt) +{ + struct imx_ic_priv *ic_priv = v4l2_get_subdevdata(sd); + struct prp_priv *priv = ic_priv->prp_priv; + struct imx_media_subdev *csi; + int ret; + + ret = v4l2_subdev_link_validate_default(sd, link, + source_fmt, sink_fmt); + if (ret) + return ret; + + csi = imx_media_find_upstream_subdev(priv->md, &ic_priv->sd.entity, + IMX_MEDIA_GRP_ID_CSI); + if (IS_ERR(csi)) + csi = NULL; + + mutex_lock(&priv->lock); + + if (priv->src_sd->grp_id & IMX_MEDIA_GRP_ID_VDIC) { + /* + * the ->PRPENC link cannot be enabled if the source + * is the VDIC + */ + if (priv->sink_sd_prpenc) + ret = -EINVAL; + goto out; + } else { + /* the source is a CSI */ + if (!csi) { + ret = -EINVAL; + goto out; + } + } + + if (csi) { + switch (csi->sd->grp_id) { + case IMX_MEDIA_GRP_ID_CSI0: + priv->csi_id = 0; + break; + case IMX_MEDIA_GRP_ID_CSI1: + priv->csi_id = 1; + break; + default: + ret = -EINVAL; + } + } else { + priv->csi_id = 0; + } + +out: + mutex_unlock(&priv->lock); + return ret; +} + +static int prp_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct imx_ic_priv *ic_priv = v4l2_get_subdevdata(sd); + struct prp_priv *priv = ic_priv->prp_priv; + int ret = 0; + + mutex_lock(&priv->lock); + + if (!priv->src_sd || (!priv->sink_sd_prpenc && !priv->sink_sd_prpvf)) { + ret = -EPIPE; + goto out; + } + + /* + * enable/disable streaming only if stream_count is + * going from 0 to 1 / 1 to 0. + */ + if (priv->stream_count != !enable) + goto update_count; + + dev_dbg(ic_priv->dev, "stream %s\n", enable ? "ON" : "OFF"); + + if (enable) + ret = prp_start(priv); + else + prp_stop(priv); + if (ret) + goto out; + + /* start/stop upstream */ + ret = v4l2_subdev_call(priv->src_sd, video, s_stream, enable); + ret = (ret && ret != -ENOIOCTLCMD) ? ret : 0; + if (ret) { + if (enable) + prp_stop(priv); + goto out; + } + +update_count: + priv->stream_count += enable ? 1 : -1; + if (priv->stream_count < 0) + priv->stream_count = 0; +out: + mutex_unlock(&priv->lock); + return ret; +} + +static int prp_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct prp_priv *priv = sd_to_priv(sd); + + if (fi->pad >= PRP_NUM_PADS) + return -EINVAL; + + mutex_lock(&priv->lock); + fi->interval = priv->frame_interval; + mutex_unlock(&priv->lock); + + return 0; +} + +static int prp_s_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct prp_priv *priv = sd_to_priv(sd); + + if (fi->pad >= PRP_NUM_PADS) + return -EINVAL; + + /* No limits on frame interval */ + mutex_lock(&priv->lock); + priv->frame_interval = fi->interval; + mutex_unlock(&priv->lock); + + return 0; +} + +/* + * retrieve our pads parsed from the OF graph by the media device + */ +static int prp_registered(struct v4l2_subdev *sd) +{ + struct prp_priv *priv = sd_to_priv(sd); + int i, ret; + u32 code; + + /* get media device */ + priv->md = dev_get_drvdata(sd->v4l2_dev->dev); + + for (i = 0; i < PRP_NUM_PADS; i++) { + priv->pad[i].flags = (i == PRP_SINK_PAD) ? + MEDIA_PAD_FL_SINK : MEDIA_PAD_FL_SOURCE; + } + + /* init default frame interval */ + priv->frame_interval.numerator = 1; + priv->frame_interval.denominator = 30; + + /* set a default mbus format */ + imx_media_enum_ipu_format(&code, 0, CS_SEL_YUV); + ret = imx_media_init_mbus_fmt(&priv->format_mbus, 640, 480, code, + V4L2_FIELD_NONE, NULL); + if (ret) + return ret; + + return media_entity_pads_init(&sd->entity, PRP_NUM_PADS, priv->pad); +} + +static const struct v4l2_subdev_pad_ops prp_pad_ops = { + .enum_mbus_code = prp_enum_mbus_code, + .get_fmt = prp_get_fmt, + .set_fmt = prp_set_fmt, + .link_validate = prp_link_validate, +}; + +static const struct v4l2_subdev_video_ops prp_video_ops = { + .g_frame_interval = prp_g_frame_interval, + .s_frame_interval = prp_s_frame_interval, + .s_stream = prp_s_stream, +}; + +static const struct media_entity_operations prp_entity_ops = { + .link_setup = prp_link_setup, + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct v4l2_subdev_ops prp_subdev_ops = { + .video = &prp_video_ops, + .pad = &prp_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops prp_internal_ops = { + .registered = prp_registered, +}; + +static int prp_init(struct imx_ic_priv *ic_priv) +{ + struct prp_priv *priv; + + priv = devm_kzalloc(ic_priv->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + mutex_init(&priv->lock); + ic_priv->prp_priv = priv; + priv->ic_priv = ic_priv; + + return 0; +} + +static void prp_remove(struct imx_ic_priv *ic_priv) +{ + struct prp_priv *priv = ic_priv->prp_priv; + + mutex_destroy(&priv->lock); +} + +struct imx_ic_ops imx_ic_prp_ops = { + .subdev_ops = &prp_subdev_ops, + .internal_ops = &prp_internal_ops, + .entity_ops = &prp_entity_ops, + .init = prp_init, + .remove = prp_remove, +}; diff --git a/drivers/staging/media/imx/imx-ic-prpencvf.c b/drivers/staging/media/imx/imx-ic-prpencvf.c new file mode 100644 index 000000000000..ed363fe3b3d0 --- /dev/null +++ b/drivers/staging/media/imx/imx-ic-prpencvf.c @@ -0,0 +1,1309 @@ +/* + * V4L2 Capture IC Preprocess Subdev for Freescale i.MX5/6 SOC + * + * This subdevice handles capture of video frames from the CSI or VDIC, + * which are routed directly to the Image Converter preprocess tasks, + * for resizing, colorspace conversion, and rotation. + * + * Copyright (c) 2012-2017 Mentor Graphics Inc. + * + * 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; either version 2 of the License, or + * (at your option) any later version. + */ +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/timer.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-mc.h> +#include <media/v4l2-subdev.h> +#include <media/imx.h> +#include "imx-media.h" +#include "imx-ic.h" + +/* + * Min/Max supported width and heights. + * + * We allow planar output, so we have to align width at the source pad + * by 16 pixels to meet IDMAC alignment requirements for possible planar + * output. + * + * TODO: move this into pad format negotiation, if capture device + * has not requested a planar format, we should allow 8 pixel + * alignment at the source pad. + */ +#define MIN_W_SINK 176 +#define MIN_H_SINK 144 +#define MAX_W_SINK 4096 +#define MAX_H_SINK 4096 +#define W_ALIGN_SINK 3 /* multiple of 8 pixels */ +#define H_ALIGN_SINK 1 /* multiple of 2 lines */ + +#define MAX_W_SRC 1024 +#define MAX_H_SRC 1024 +#define W_ALIGN_SRC 4 /* multiple of 16 pixels */ +#define H_ALIGN_SRC 1 /* multiple of 2 lines */ + +#define S_ALIGN 1 /* multiple of 2 */ + +struct prp_priv { + struct imx_media_dev *md; + struct imx_ic_priv *ic_priv; + struct media_pad pad[PRPENCVF_NUM_PADS]; + /* the video device at output pad */ + struct imx_media_video_dev *vdev; + + /* lock to protect all members below */ + struct mutex lock; + + /* IPU units we require */ + struct ipu_soc *ipu; + struct ipu_ic *ic; + struct ipuv3_channel *out_ch; + struct ipuv3_channel *rot_in_ch; + struct ipuv3_channel *rot_out_ch; + + /* active vb2 buffers to send to video dev sink */ + struct imx_media_buffer *active_vb2_buf[2]; + struct imx_media_dma_buf underrun_buf; + + int ipu_buf_num; /* ipu double buffer index: 0-1 */ + + /* the sink for the captured frames */ + struct media_entity *sink; + /* the source subdev */ + struct v4l2_subdev *src_sd; + + struct v4l2_mbus_framefmt format_mbus[PRPENCVF_NUM_PADS]; + const struct imx_media_pixfmt *cc[PRPENCVF_NUM_PADS]; + struct v4l2_fract frame_interval; + + struct imx_media_dma_buf rot_buf[2]; + + /* controls */ + struct v4l2_ctrl_handler ctrl_hdlr; + int rotation; /* degrees */ + bool hflip; + bool vflip; + + /* derived from rotation, hflip, vflip controls */ + enum ipu_rotate_mode rot_mode; + + spinlock_t irqlock; /* protect eof_irq handler */ + + struct timer_list eof_timeout_timer; + int eof_irq; + int nfb4eof_irq; + + int stream_count; + bool last_eof; /* waiting for last EOF at stream off */ + bool nfb4eof; /* NFB4EOF encountered during streaming */ + struct completion last_eof_comp; +}; + +static const struct prp_channels { + u32 out_ch; + u32 rot_in_ch; + u32 rot_out_ch; +} prp_channel[] = { + [IC_TASK_ENCODER] = { + .out_ch = IPUV3_CHANNEL_IC_PRP_ENC_MEM, + .rot_in_ch = IPUV3_CHANNEL_MEM_ROT_ENC, + .rot_out_ch = IPUV3_CHANNEL_ROT_ENC_MEM, + }, + [IC_TASK_VIEWFINDER] = { + .out_ch = IPUV3_CHANNEL_IC_PRP_VF_MEM, + .rot_in_ch = IPUV3_CHANNEL_MEM_ROT_VF, + .rot_out_ch = IPUV3_CHANNEL_ROT_VF_MEM, + }, +}; + +static inline struct prp_priv *sd_to_priv(struct v4l2_subdev *sd) +{ + struct imx_ic_priv *ic_priv = v4l2_get_subdevdata(sd); + + return ic_priv->task_priv; +} + +static void prp_put_ipu_resources(struct prp_priv *priv) +{ + if (!IS_ERR_OR_NULL(priv->ic)) + ipu_ic_put(priv->ic); + priv->ic = NULL; + + if (!IS_ERR_OR_NULL(priv->out_ch)) + ipu_idmac_put(priv->out_ch); + priv->out_ch = NULL; + + if (!IS_ERR_OR_NULL(priv->rot_in_ch)) + ipu_idmac_put(priv->rot_in_ch); + priv->rot_in_ch = NULL; + + if (!IS_ERR_OR_NULL(priv->rot_out_ch)) + ipu_idmac_put(priv->rot_out_ch); + priv->rot_out_ch = NULL; +} + +static int prp_get_ipu_resources(struct prp_priv *priv) +{ + struct imx_ic_priv *ic_priv = priv->ic_priv; + int ret, task = ic_priv->task_id; + + priv->ipu = priv->md->ipu[ic_priv->ipu_id]; + + priv->ic = ipu_ic_get(priv->ipu, task); + if (IS_ERR(priv->ic)) { + v4l2_err(&ic_priv->sd, "failed to get IC\n"); + ret = PTR_ERR(priv->ic); + goto out; + } + + priv->out_ch = ipu_idmac_get(priv->ipu, + prp_channel[task].out_ch); + if (IS_ERR(priv->out_ch)) { + v4l2_err(&ic_priv->sd, "could not get IDMAC channel %u\n", + prp_channel[task].out_ch); + ret = PTR_ERR(priv->out_ch); + goto out; + } + + priv->rot_in_ch = ipu_idmac_get(priv->ipu, + prp_channel[task].rot_in_ch); + if (IS_ERR(priv->rot_in_ch)) { + v4l2_err(&ic_priv->sd, "could not get IDMAC channel %u\n", + prp_channel[task].rot_in_ch); + ret = PTR_ERR(priv->rot_in_ch); + goto out; + } + + priv->rot_out_ch = ipu_idmac_get(priv->ipu, + prp_channel[task].rot_out_ch); + if (IS_ERR(priv->rot_out_ch)) { + v4l2_err(&ic_priv->sd, "could not get IDMAC channel %u\n", + prp_channel[task].rot_out_ch); + ret = PTR_ERR(priv->rot_out_ch); + goto out; + } + + return 0; +out: + prp_put_ipu_resources(priv); + return ret; +} + +static void prp_vb2_buf_done(struct prp_priv *priv, struct ipuv3_channel *ch) +{ + struct imx_media_video_dev *vdev = priv->vdev; + struct imx_media_buffer *done, *next; + struct vb2_buffer *vb; + dma_addr_t phys; + + done = priv->active_vb2_buf[priv->ipu_buf_num]; + if (done) { + vb = &done->vbuf.vb2_buf; + vb->timestamp = ktime_get_ns(); + vb2_buffer_done(vb, priv->nfb4eof ? + VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE); + } + + priv->nfb4eof = false; + + /* get next queued buffer */ + next = imx_media_capture_device_next_buf(vdev); + if (next) { + phys = vb2_dma_contig_plane_dma_addr(&next->vbuf.vb2_buf, 0); + priv->active_vb2_buf[priv->ipu_buf_num] = next; + } else { + phys = priv->underrun_buf.phys; + priv->active_vb2_buf[priv->ipu_buf_num] = NULL; + } + + if (ipu_idmac_buffer_is_ready(ch, priv->ipu_buf_num)) + ipu_idmac_clear_buffer(ch, priv->ipu_buf_num); + + ipu_cpmem_set_buffer(ch, priv->ipu_buf_num, phys); +} + +static irqreturn_t prp_eof_interrupt(int irq, void *dev_id) +{ + struct prp_priv *priv = dev_id; + struct ipuv3_channel *channel; + + spin_lock(&priv->irqlock); + + if (priv->last_eof) { + complete(&priv->last_eof_comp); + priv->last_eof = false; + goto unlock; + } + + channel = (ipu_rot_mode_is_irt(priv->rot_mode)) ? + priv->rot_out_ch : priv->out_ch; + + prp_vb2_buf_done(priv, channel); + + /* select new IPU buf */ + ipu_idmac_select_buffer(channel, priv->ipu_buf_num); + /* toggle IPU double-buffer index */ + priv->ipu_buf_num ^= 1; + + /* bump the EOF timeout timer */ + mod_timer(&priv->eof_timeout_timer, + jiffies + msecs_to_jiffies(IMX_MEDIA_EOF_TIMEOUT)); + +unlock: + spin_unlock(&priv->irqlock); + return IRQ_HANDLED; +} + +static irqreturn_t prp_nfb4eof_interrupt(int irq, void *dev_id) +{ + struct prp_priv *priv = dev_id; + struct imx_ic_priv *ic_priv = priv->ic_priv; + + spin_lock(&priv->irqlock); + + /* + * this is not an unrecoverable error, just mark + * the next captured frame with vb2 error flag. + */ + priv->nfb4eof = true; + + v4l2_err(&ic_priv->sd, "NFB4EOF\n"); + + spin_unlock(&priv->irqlock); + + return IRQ_HANDLED; +} + +/* + * EOF timeout timer function. + */ +/* + * EOF timeout timer function. This is an unrecoverable condition + * without a stream restart. + */ +static void prp_eof_timeout(unsigned long data) +{ + struct prp_priv *priv = (struct prp_priv *)data; + struct imx_media_video_dev *vdev = priv->vdev; + struct imx_ic_priv *ic_priv = priv->ic_priv; + + v4l2_err(&ic_priv->sd, "EOF timeout\n"); + + /* signal a fatal error to capture device */ + imx_media_capture_device_error(vdev); +} + +static void prp_setup_vb2_buf(struct prp_priv *priv, dma_addr_t *phys) +{ + struct imx_media_video_dev *vdev = priv->vdev; + struct imx_media_buffer *buf; + int i; + + for (i = 0; i < 2; i++) { + buf = imx_media_capture_device_next_buf(vdev); + if (buf) { + priv->active_vb2_buf[i] = buf; + phys[i] = vb2_dma_contig_plane_dma_addr( + &buf->vbuf.vb2_buf, 0); + } else { + priv->active_vb2_buf[i] = NULL; + phys[i] = priv->underrun_buf.phys; + } + } +} + +static void prp_unsetup_vb2_buf(struct prp_priv *priv, + enum vb2_buffer_state return_status) +{ + struct imx_media_buffer *buf; + int i; + + /* return any remaining active frames with return_status */ + for (i = 0; i < 2; i++) { + buf = priv->active_vb2_buf[i]; + if (buf) { + struct vb2_buffer *vb = &buf->vbuf.vb2_buf; + + vb->timestamp = ktime_get_ns(); + vb2_buffer_done(vb, return_status); + } + } +} + +static int prp_setup_channel(struct prp_priv *priv, + struct ipuv3_channel *channel, + enum ipu_rotate_mode rot_mode, + dma_addr_t addr0, dma_addr_t addr1, + bool rot_swap_width_height) +{ + struct imx_media_video_dev *vdev = priv->vdev; + const struct imx_media_pixfmt *outcc; + struct v4l2_mbus_framefmt *infmt; + unsigned int burst_size; + struct ipu_image image; + int ret; + + infmt = &priv->format_mbus[PRPENCVF_SINK_PAD]; + outcc = vdev->cc; + + ipu_cpmem_zero(channel); + + memset(&image, 0, sizeof(image)); + image.pix = vdev->fmt.fmt.pix; + image.rect.width = image.pix.width; + image.rect.height = image.pix.height; + + if (rot_swap_width_height) { + swap(image.pix.width, image.pix.height); + swap(image.rect.width, image.rect.height); + /* recalc stride using swapped width */ + image.pix.bytesperline = outcc->planar ? + image.pix.width : + (image.pix.width * outcc->bpp) >> 3; + } + + image.phys0 = addr0; + image.phys1 = addr1; + + ret = ipu_cpmem_set_image(channel, &image); + if (ret) + return ret; + + if (channel == priv->rot_in_ch || + channel == priv->rot_out_ch) { + burst_size = 8; + ipu_cpmem_set_block_mode(channel); + } else { + burst_size = (image.pix.width & 0xf) ? 8 : 16; + } + + ipu_cpmem_set_burstsize(channel, burst_size); + + if (rot_mode) + ipu_cpmem_set_rotation(channel, rot_mode); + + if (image.pix.field == V4L2_FIELD_NONE && + V4L2_FIELD_HAS_BOTH(infmt->field) && + channel == priv->out_ch) + ipu_cpmem_interlaced_scan(channel, image.pix.bytesperline); + + ret = ipu_ic_task_idma_init(priv->ic, channel, + image.pix.width, image.pix.height, + burst_size, rot_mode); + if (ret) + return ret; + + ipu_cpmem_set_axi_id(channel, 1); + + ipu_idmac_set_double_buffer(channel, true); + + return 0; +} + +static int prp_setup_rotation(struct prp_priv *priv) +{ + struct imx_media_video_dev *vdev = priv->vdev; + struct imx_ic_priv *ic_priv = priv->ic_priv; + const struct imx_media_pixfmt *outcc, *incc; + struct v4l2_mbus_framefmt *infmt; + struct v4l2_pix_format *outfmt; + dma_addr_t phys[2]; + int ret; + + infmt = &priv->format_mbus[PRPENCVF_SINK_PAD]; + outfmt = &vdev->fmt.fmt.pix; + incc = priv->cc[PRPENCVF_SINK_PAD]; + outcc = vdev->cc; + + ret = imx_media_alloc_dma_buf(priv->md, &priv->rot_buf[0], + outfmt->sizeimage); + if (ret) { + v4l2_err(&ic_priv->sd, "failed to alloc rot_buf[0], %d\n", ret); + return ret; + } + ret = imx_media_alloc_dma_buf(priv->md, &priv->rot_buf[1], + outfmt->sizeimage); + if (ret) { + v4l2_err(&ic_priv->sd, "failed to alloc rot_buf[1], %d\n", ret); + goto free_rot0; + } + + ret = ipu_ic_task_init(priv->ic, + infmt->width, infmt->height, + outfmt->height, outfmt->width, + incc->cs, outcc->cs); + if (ret) { + v4l2_err(&ic_priv->sd, "ipu_ic_task_init failed, %d\n", ret); + goto free_rot1; + } + + /* init the IC-PRP-->MEM IDMAC channel */ + ret = prp_setup_channel(priv, priv->out_ch, IPU_ROTATE_NONE, + priv->rot_buf[0].phys, priv->rot_buf[1].phys, + true); + if (ret) { + v4l2_err(&ic_priv->sd, + "prp_setup_channel(out_ch) failed, %d\n", ret); + goto free_rot1; + } + + /* init the MEM-->IC-PRP ROT IDMAC channel */ + ret = prp_setup_channel(priv, priv->rot_in_ch, priv->rot_mode, + priv->rot_buf[0].phys, priv->rot_buf[1].phys, + true); + if (ret) { + v4l2_err(&ic_priv->sd, + "prp_setup_channel(rot_in_ch) failed, %d\n", ret); + goto free_rot1; + } + + prp_setup_vb2_buf(priv, phys); + + /* init the destination IC-PRP ROT-->MEM IDMAC channel */ + ret = prp_setup_channel(priv, priv->rot_out_ch, IPU_ROTATE_NONE, + phys[0], phys[1], + false); + if (ret) { + v4l2_err(&ic_priv->sd, + "prp_setup_channel(rot_out_ch) failed, %d\n", ret); + goto unsetup_vb2; + } + + /* now link IC-PRP-->MEM to MEM-->IC-PRP ROT */ + ipu_idmac_link(priv->out_ch, priv->rot_in_ch); + + /* enable the IC */ + ipu_ic_enable(priv->ic); + + /* set buffers ready */ + ipu_idmac_select_buffer(priv->out_ch, 0); + ipu_idmac_select_buffer(priv->out_ch, 1); + ipu_idmac_select_buffer(priv->rot_out_ch, 0); + ipu_idmac_select_buffer(priv->rot_out_ch, 1); + + /* enable the channels */ + ipu_idmac_enable_channel(priv->out_ch); + ipu_idmac_enable_channel(priv->rot_in_ch); + ipu_idmac_enable_channel(priv->rot_out_ch); + + /* and finally enable the IC PRP task */ + ipu_ic_task_enable(priv->ic); + + return 0; + +unsetup_vb2: + prp_unsetup_vb2_buf(priv, VB2_BUF_STATE_QUEUED); +free_rot1: + imx_media_free_dma_buf(priv->md, &priv->rot_buf[1]); +free_rot0: + imx_media_free_dma_buf(priv->md, &priv->rot_buf[0]); + return ret; +} + +static void prp_unsetup_rotation(struct prp_priv *priv) +{ + ipu_ic_task_disable(priv->ic); + + ipu_idmac_disable_channel(priv->out_ch); + ipu_idmac_disable_channel(priv->rot_in_ch); + ipu_idmac_disable_channel(priv->rot_out_ch); + + ipu_idmac_unlink(priv->out_ch, priv->rot_in_ch); + + ipu_ic_disable(priv->ic); + + imx_media_free_dma_buf(priv->md, &priv->rot_buf[0]); + imx_media_free_dma_buf(priv->md, &priv->rot_buf[1]); +} + +static int prp_setup_norotation(struct prp_priv *priv) +{ + struct imx_media_video_dev *vdev = priv->vdev; + struct imx_ic_priv *ic_priv = priv->ic_priv; + const struct imx_media_pixfmt *outcc, *incc; + struct v4l2_mbus_framefmt *infmt; + struct v4l2_pix_format *outfmt; + dma_addr_t phys[2]; + int ret; + + infmt = &priv->format_mbus[PRPENCVF_SINK_PAD]; + outfmt = &vdev->fmt.fmt.pix; + incc = priv->cc[PRPENCVF_SINK_PAD]; + outcc = vdev->cc; + + ret = ipu_ic_task_init(priv->ic, + infmt->width, infmt->height, + outfmt->width, outfmt->height, + incc->cs, outcc->cs); + if (ret) { + v4l2_err(&ic_priv->sd, "ipu_ic_task_init failed, %d\n", ret); + return ret; + } + + prp_setup_vb2_buf(priv, phys); + + /* init the IC PRP-->MEM IDMAC channel */ + ret = prp_setup_channel(priv, priv->out_ch, priv->rot_mode, + phys[0], phys[1], false); + if (ret) { + v4l2_err(&ic_priv->sd, + "prp_setup_channel(out_ch) failed, %d\n", ret); + goto unsetup_vb2; + } + + ipu_cpmem_dump(priv->out_ch); + ipu_ic_dump(priv->ic); + ipu_dump(priv->ipu); + + ipu_ic_enable(priv->ic); + + /* set buffers ready */ + ipu_idmac_select_buffer(priv->out_ch, 0); + ipu_idmac_select_buffer(priv->out_ch, 1); + + /* enable the channels */ + ipu_idmac_enable_channel(priv->out_ch); + + /* enable the IC task */ + ipu_ic_task_enable(priv->ic); + + return 0; + +unsetup_vb2: + prp_unsetup_vb2_buf(priv, VB2_BUF_STATE_QUEUED); + return ret; +} + +static void prp_unsetup_norotation(struct prp_priv *priv) +{ + ipu_ic_task_disable(priv->ic); + ipu_idmac_disable_channel(priv->out_ch); + ipu_ic_disable(priv->ic); +} + +static void prp_unsetup(struct prp_priv *priv, + enum vb2_buffer_state state) +{ + if (ipu_rot_mode_is_irt(priv->rot_mode)) + prp_unsetup_rotation(priv); + else + prp_unsetup_norotation(priv); + + prp_unsetup_vb2_buf(priv, state); +} + +static int prp_start(struct prp_priv *priv) +{ + struct imx_ic_priv *ic_priv = priv->ic_priv; + struct imx_media_video_dev *vdev = priv->vdev; + struct v4l2_pix_format *outfmt; + int ret; + + ret = prp_get_ipu_resources(priv); + if (ret) + return ret; + + outfmt = &vdev->fmt.fmt.pix; + + ret = imx_media_alloc_dma_buf(priv->md, &priv->underrun_buf, + outfmt->sizeimage); + if (ret) + goto out_put_ipu; + + priv->ipu_buf_num = 0; + + /* init EOF completion waitq */ + init_completion(&priv->last_eof_comp); + priv->last_eof = false; + priv->nfb4eof = false; + + if (ipu_rot_mode_is_irt(priv->rot_mode)) + ret = prp_setup_rotation(priv); + else + ret = prp_setup_norotation(priv); + if (ret) + goto out_free_underrun; + + priv->nfb4eof_irq = ipu_idmac_channel_irq(priv->ipu, + priv->out_ch, + IPU_IRQ_NFB4EOF); + ret = devm_request_irq(ic_priv->dev, priv->nfb4eof_irq, + prp_nfb4eof_interrupt, 0, + "imx-ic-prp-nfb4eof", priv); + if (ret) { + v4l2_err(&ic_priv->sd, + "Error registering NFB4EOF irq: %d\n", ret); + goto out_unsetup; + } + + if (ipu_rot_mode_is_irt(priv->rot_mode)) + priv->eof_irq = ipu_idmac_channel_irq( + priv->ipu, priv->rot_out_ch, IPU_IRQ_EOF); + else + priv->eof_irq = ipu_idmac_channel_irq( + priv->ipu, priv->out_ch, IPU_IRQ_EOF); + + ret = devm_request_irq(ic_priv->dev, priv->eof_irq, + prp_eof_interrupt, 0, + "imx-ic-prp-eof", priv); + if (ret) { + v4l2_err(&ic_priv->sd, + "Error registering eof irq: %d\n", ret); + goto out_free_nfb4eof_irq; + } + + /* start the EOF timeout timer */ + mod_timer(&priv->eof_timeout_timer, + jiffies + msecs_to_jiffies(IMX_MEDIA_EOF_TIMEOUT)); + + return 0; + +out_free_nfb4eof_irq: + devm_free_irq(ic_priv->dev, priv->nfb4eof_irq, priv); +out_unsetup: + prp_unsetup(priv, VB2_BUF_STATE_QUEUED); +out_free_underrun: + imx_media_free_dma_buf(priv->md, &priv->underrun_buf); +out_put_ipu: + prp_put_ipu_resources(priv); + return ret; +} + +static void prp_stop(struct prp_priv *priv) +{ + struct imx_ic_priv *ic_priv = priv->ic_priv; + unsigned long flags; + int ret; + + /* mark next EOF interrupt as the last before stream off */ + spin_lock_irqsave(&priv->irqlock, flags); + priv->last_eof = true; + spin_unlock_irqrestore(&priv->irqlock, flags); + + /* + * and then wait for interrupt handler to mark completion. + */ + ret = wait_for_completion_timeout( + &priv->last_eof_comp, + msecs_to_jiffies(IMX_MEDIA_EOF_TIMEOUT)); + if (ret == 0) + v4l2_warn(&ic_priv->sd, "wait last EOF timeout\n"); + + devm_free_irq(ic_priv->dev, priv->eof_irq, priv); + devm_free_irq(ic_priv->dev, priv->nfb4eof_irq, priv); + + prp_unsetup(priv, VB2_BUF_STATE_ERROR); + + imx_media_free_dma_buf(priv->md, &priv->underrun_buf); + + /* cancel the EOF timeout timer */ + del_timer_sync(&priv->eof_timeout_timer); + + prp_put_ipu_resources(priv); +} + +static struct v4l2_mbus_framefmt * +__prp_get_fmt(struct prp_priv *priv, struct v4l2_subdev_pad_config *cfg, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + struct imx_ic_priv *ic_priv = priv->ic_priv; + + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(&ic_priv->sd, cfg, pad); + else + return &priv->format_mbus[pad]; +} + +/* + * Applies IC resizer and IDMAC alignment restrictions to output + * rectangle given the input rectangle, and depending on given + * rotation mode. + * + * The IC resizer cannot downsize more than 4:1. Note also that + * for 90 or 270 rotation, _both_ output width and height must + * be aligned by W_ALIGN_SRC, because the intermediate rotation + * buffer swaps output width/height, and the final output buffer + * does not. + * + * Returns true if the output rectangle was modified. + */ +static bool prp_bound_align_output(struct v4l2_mbus_framefmt *outfmt, + struct v4l2_mbus_framefmt *infmt, + enum ipu_rotate_mode rot_mode) +{ + u32 orig_width = outfmt->width; + u32 orig_height = outfmt->height; + + if (ipu_rot_mode_is_irt(rot_mode)) + v4l_bound_align_image(&outfmt->width, + infmt->height / 4, MAX_H_SRC, + W_ALIGN_SRC, + &outfmt->height, + infmt->width / 4, MAX_W_SRC, + W_ALIGN_SRC, S_ALIGN); + else + v4l_bound_align_image(&outfmt->width, + infmt->width / 4, MAX_W_SRC, + W_ALIGN_SRC, + &outfmt->height, + infmt->height / 4, MAX_H_SRC, + H_ALIGN_SRC, S_ALIGN); + + return outfmt->width != orig_width || outfmt->height != orig_height; +} + +/* + * V4L2 subdev operations. + */ + +static int prp_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad >= PRPENCVF_NUM_PADS) + return -EINVAL; + + return imx_media_enum_ipu_format(&code->code, code->index, CS_SEL_ANY); +} + +static int prp_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *sdformat) +{ + struct prp_priv *priv = sd_to_priv(sd); + struct v4l2_mbus_framefmt *fmt; + int ret = 0; + + if (sdformat->pad >= PRPENCVF_NUM_PADS) + return -EINVAL; + + mutex_lock(&priv->lock); + + fmt = __prp_get_fmt(priv, cfg, sdformat->pad, sdformat->which); + if (!fmt) { + ret = -EINVAL; + goto out; + } + + sdformat->format = *fmt; +out: + mutex_unlock(&priv->lock); + return ret; +} + +static void prp_try_fmt(struct prp_priv *priv, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *sdformat, + const struct imx_media_pixfmt **cc) +{ + struct v4l2_mbus_framefmt *infmt; + + *cc = imx_media_find_ipu_format(sdformat->format.code, CS_SEL_ANY); + if (!*cc) { + u32 code; + + imx_media_enum_ipu_format(&code, 0, CS_SEL_ANY); + *cc = imx_media_find_ipu_format(code, CS_SEL_ANY); + sdformat->format.code = (*cc)->codes[0]; + } + + infmt = __prp_get_fmt(priv, cfg, PRPENCVF_SINK_PAD, sdformat->which); + + if (sdformat->pad == PRPENCVF_SRC_PAD) { + if (sdformat->format.field != V4L2_FIELD_NONE) + sdformat->format.field = infmt->field; + + prp_bound_align_output(&sdformat->format, infmt, + priv->rot_mode); + + /* propagate colorimetry from sink */ + sdformat->format.colorspace = infmt->colorspace; + sdformat->format.xfer_func = infmt->xfer_func; + sdformat->format.quantization = infmt->quantization; + sdformat->format.ycbcr_enc = infmt->ycbcr_enc; + } else { + v4l_bound_align_image(&sdformat->format.width, + MIN_W_SINK, MAX_W_SINK, W_ALIGN_SINK, + &sdformat->format.height, + MIN_H_SINK, MAX_H_SINK, H_ALIGN_SINK, + S_ALIGN); + + imx_media_fill_default_mbus_fields(&sdformat->format, infmt, + true); + } +} + +static int prp_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *sdformat) +{ + struct prp_priv *priv = sd_to_priv(sd); + struct imx_media_video_dev *vdev = priv->vdev; + const struct imx_media_pixfmt *cc; + struct v4l2_pix_format vdev_fmt; + struct v4l2_mbus_framefmt *fmt; + int ret = 0; + + if (sdformat->pad >= PRPENCVF_NUM_PADS) + return -EINVAL; + + mutex_lock(&priv->lock); + + if (priv->stream_count > 0) { + ret = -EBUSY; + goto out; + } + + prp_try_fmt(priv, cfg, sdformat, &cc); + + fmt = __prp_get_fmt(priv, cfg, sdformat->pad, sdformat->which); + *fmt = sdformat->format; + + /* propagate a default format to source pad */ + if (sdformat->pad == PRPENCVF_SINK_PAD) { + const struct imx_media_pixfmt *outcc; + struct v4l2_mbus_framefmt *outfmt; + struct v4l2_subdev_format format; + + format.pad = PRPENCVF_SRC_PAD; + format.which = sdformat->which; + format.format = sdformat->format; + prp_try_fmt(priv, cfg, &format, &outcc); + + outfmt = __prp_get_fmt(priv, cfg, PRPENCVF_SRC_PAD, + sdformat->which); + *outfmt = format.format; + if (sdformat->which == V4L2_SUBDEV_FORMAT_ACTIVE) + priv->cc[PRPENCVF_SRC_PAD] = outcc; + } + + if (sdformat->which == V4L2_SUBDEV_FORMAT_TRY) + goto out; + + priv->cc[sdformat->pad] = cc; + + /* propagate output pad format to capture device */ + imx_media_mbus_fmt_to_pix_fmt(&vdev_fmt, + &priv->format_mbus[PRPENCVF_SRC_PAD], + priv->cc[PRPENCVF_SRC_PAD]); + mutex_unlock(&priv->lock); + imx_media_capture_device_set_format(vdev, &vdev_fmt); + + return 0; +out: + mutex_unlock(&priv->lock); + return ret; +} + +static int prp_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct prp_priv *priv = sd_to_priv(sd); + struct v4l2_subdev_format format = {0}; + const struct imx_media_pixfmt *cc; + int ret = 0; + + if (fse->pad >= PRPENCVF_NUM_PADS || fse->index != 0) + return -EINVAL; + + mutex_lock(&priv->lock); + + format.pad = fse->pad; + format.which = fse->which; + format.format.code = fse->code; + format.format.width = 1; + format.format.height = 1; + prp_try_fmt(priv, cfg, &format, &cc); + fse->min_width = format.format.width; + fse->min_height = format.format.height; + + if (format.format.code != fse->code) { + ret = -EINVAL; + goto out; + } + + format.format.code = fse->code; + format.format.width = -1; + format.format.height = -1; + prp_try_fmt(priv, cfg, &format, &cc); + fse->max_width = format.format.width; + fse->max_height = format.format.height; +out: + mutex_unlock(&priv->lock); + return ret; +} + +static int prp_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); + struct imx_ic_priv *ic_priv = v4l2_get_subdevdata(sd); + struct prp_priv *priv = ic_priv->task_priv; + struct v4l2_subdev *remote_sd; + int ret = 0; + + dev_dbg(ic_priv->dev, "link setup %s -> %s", remote->entity->name, + local->entity->name); + + mutex_lock(&priv->lock); + + if (local->flags & MEDIA_PAD_FL_SINK) { + if (!is_media_entity_v4l2_subdev(remote->entity)) { + ret = -EINVAL; + goto out; + } + + remote_sd = media_entity_to_v4l2_subdev(remote->entity); + + if (flags & MEDIA_LNK_FL_ENABLED) { + if (priv->src_sd) { + ret = -EBUSY; + goto out; + } + priv->src_sd = remote_sd; + } else { + priv->src_sd = NULL; + } + + goto out; + } + + /* this is the source pad */ + + /* the remote must be the device node */ + if (!is_media_entity_v4l2_video_device(remote->entity)) { + ret = -EINVAL; + goto out; + } + + if (flags & MEDIA_LNK_FL_ENABLED) { + if (priv->sink) { + ret = -EBUSY; + goto out; + } + } else { + priv->sink = NULL; + goto out; + } + + priv->sink = remote->entity; +out: + mutex_unlock(&priv->lock); + return ret; +} + +static int prp_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct prp_priv *priv = container_of(ctrl->handler, + struct prp_priv, ctrl_hdlr); + struct imx_ic_priv *ic_priv = priv->ic_priv; + enum ipu_rotate_mode rot_mode; + int rotation, ret = 0; + bool hflip, vflip; + + mutex_lock(&priv->lock); + + rotation = priv->rotation; + hflip = priv->hflip; + vflip = priv->vflip; + + switch (ctrl->id) { + case V4L2_CID_HFLIP: + hflip = (ctrl->val == 1); + break; + case V4L2_CID_VFLIP: + vflip = (ctrl->val == 1); + break; + case V4L2_CID_ROTATE: + rotation = ctrl->val; + break; + default: + v4l2_err(&ic_priv->sd, "Invalid control\n"); + ret = -EINVAL; + goto out; + } + + ret = ipu_degrees_to_rot_mode(&rot_mode, rotation, hflip, vflip); + if (ret) + goto out; + + if (rot_mode != priv->rot_mode) { + struct v4l2_mbus_framefmt outfmt, infmt; + + /* can't change rotation mid-streaming */ + if (priv->stream_count > 0) { + ret = -EBUSY; + goto out; + } + + outfmt = priv->format_mbus[PRPENCVF_SRC_PAD]; + infmt = priv->format_mbus[PRPENCVF_SINK_PAD]; + + if (prp_bound_align_output(&outfmt, &infmt, rot_mode)) { + ret = -EINVAL; + goto out; + } + + priv->rot_mode = rot_mode; + priv->rotation = rotation; + priv->hflip = hflip; + priv->vflip = vflip; + } + +out: + mutex_unlock(&priv->lock); + return ret; +} + +static const struct v4l2_ctrl_ops prp_ctrl_ops = { + .s_ctrl = prp_s_ctrl, +}; + +static int prp_init_controls(struct prp_priv *priv) +{ + struct imx_ic_priv *ic_priv = priv->ic_priv; + struct v4l2_ctrl_handler *hdlr = &priv->ctrl_hdlr; + int ret; + + v4l2_ctrl_handler_init(hdlr, 3); + + v4l2_ctrl_new_std(hdlr, &prp_ctrl_ops, V4L2_CID_HFLIP, + 0, 1, 1, 0); + v4l2_ctrl_new_std(hdlr, &prp_ctrl_ops, V4L2_CID_VFLIP, + 0, 1, 1, 0); + v4l2_ctrl_new_std(hdlr, &prp_ctrl_ops, V4L2_CID_ROTATE, + 0, 270, 90, 0); + + ic_priv->sd.ctrl_handler = hdlr; + + if (hdlr->error) { + ret = hdlr->error; + goto out_free; + } + + v4l2_ctrl_handler_setup(hdlr); + return 0; + +out_free: + v4l2_ctrl_handler_free(hdlr); + return ret; +} + +static int prp_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct imx_ic_priv *ic_priv = v4l2_get_subdevdata(sd); + struct prp_priv *priv = ic_priv->task_priv; + int ret = 0; + + mutex_lock(&priv->lock); + + if (!priv->src_sd || !priv->sink) { + ret = -EPIPE; + goto out; + } + + /* + * enable/disable streaming only if stream_count is + * going from 0 to 1 / 1 to 0. + */ + if (priv->stream_count != !enable) + goto update_count; + + dev_dbg(ic_priv->dev, "stream %s\n", enable ? "ON" : "OFF"); + + if (enable) + ret = prp_start(priv); + else + prp_stop(priv); + if (ret) + goto out; + + /* start/stop upstream */ + ret = v4l2_subdev_call(priv->src_sd, video, s_stream, enable); + ret = (ret && ret != -ENOIOCTLCMD) ? ret : 0; + if (ret) { + if (enable) + prp_stop(priv); + goto out; + } + +update_count: + priv->stream_count += enable ? 1 : -1; + if (priv->stream_count < 0) + priv->stream_count = 0; +out: + mutex_unlock(&priv->lock); + return ret; +} + +static int prp_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct prp_priv *priv = sd_to_priv(sd); + + if (fi->pad >= PRPENCVF_NUM_PADS) + return -EINVAL; + + mutex_lock(&priv->lock); + fi->interval = priv->frame_interval; + mutex_unlock(&priv->lock); + + return 0; +} + +static int prp_s_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct prp_priv *priv = sd_to_priv(sd); + + if (fi->pad >= PRPENCVF_NUM_PADS) + return -EINVAL; + + /* No limits on frame interval */ + mutex_lock(&priv->lock); + priv->frame_interval = fi->interval; + mutex_unlock(&priv->lock); + + return 0; +} + +/* + * retrieve our pads parsed from the OF graph by the media device + */ +static int prp_registered(struct v4l2_subdev *sd) +{ + struct prp_priv *priv = sd_to_priv(sd); + int i, ret; + u32 code; + + /* get media device */ + priv->md = dev_get_drvdata(sd->v4l2_dev->dev); + + for (i = 0; i < PRPENCVF_NUM_PADS; i++) { + priv->pad[i].flags = (i == PRPENCVF_SINK_PAD) ? + MEDIA_PAD_FL_SINK : MEDIA_PAD_FL_SOURCE; + + /* set a default mbus format */ + imx_media_enum_ipu_format(&code, 0, CS_SEL_YUV); + ret = imx_media_init_mbus_fmt(&priv->format_mbus[i], + 640, 480, code, V4L2_FIELD_NONE, + &priv->cc[i]); + if (ret) + return ret; + } + + /* init default frame interval */ + priv->frame_interval.numerator = 1; + priv->frame_interval.denominator = 30; + + ret = media_entity_pads_init(&sd->entity, PRPENCVF_NUM_PADS, + priv->pad); + if (ret) + return ret; + + ret = imx_media_capture_device_register(priv->vdev); + if (ret) + return ret; + + ret = imx_media_add_video_device(priv->md, priv->vdev); + if (ret) + goto unreg; + + ret = prp_init_controls(priv); + if (ret) + goto unreg; + + return 0; +unreg: + imx_media_capture_device_unregister(priv->vdev); + return ret; +} + +static void prp_unregistered(struct v4l2_subdev *sd) +{ + struct prp_priv *priv = sd_to_priv(sd); + + imx_media_capture_device_unregister(priv->vdev); + v4l2_ctrl_handler_free(&priv->ctrl_hdlr); +} + +static const struct v4l2_subdev_pad_ops prp_pad_ops = { + .enum_mbus_code = prp_enum_mbus_code, + .enum_frame_size = prp_enum_frame_size, + .get_fmt = prp_get_fmt, + .set_fmt = prp_set_fmt, +}; + +static const struct v4l2_subdev_video_ops prp_video_ops = { + .g_frame_interval = prp_g_frame_interval, + .s_frame_interval = prp_s_frame_interval, + .s_stream = prp_s_stream, +}; + +static const struct media_entity_operations prp_entity_ops = { + .link_setup = prp_link_setup, + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct v4l2_subdev_ops prp_subdev_ops = { + .video = &prp_video_ops, + .pad = &prp_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops prp_internal_ops = { + .registered = prp_registered, + .unregistered = prp_unregistered, +}; + +static int prp_init(struct imx_ic_priv *ic_priv) +{ + struct prp_priv *priv; + + priv = devm_kzalloc(ic_priv->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + ic_priv->task_priv = priv; + priv->ic_priv = ic_priv; + + spin_lock_init(&priv->irqlock); + init_timer(&priv->eof_timeout_timer); + priv->eof_timeout_timer.data = (unsigned long)priv; + priv->eof_timeout_timer.function = prp_eof_timeout; + + priv->vdev = imx_media_capture_device_init(&ic_priv->sd, + PRPENCVF_SRC_PAD); + if (IS_ERR(priv->vdev)) + return PTR_ERR(priv->vdev); + + mutex_init(&priv->lock); + + return 0; +} + +static void prp_remove(struct imx_ic_priv *ic_priv) +{ + struct prp_priv *priv = ic_priv->task_priv; + + mutex_destroy(&priv->lock); + imx_media_capture_device_remove(priv->vdev); +} + +struct imx_ic_ops imx_ic_prpencvf_ops = { + .subdev_ops = &prp_subdev_ops, + .internal_ops = &prp_internal_ops, + .entity_ops = &prp_entity_ops, + .init = prp_init, + .remove = prp_remove, +}; diff --git a/drivers/staging/media/imx/imx-ic.h b/drivers/staging/media/imx/imx-ic.h new file mode 100644 index 000000000000..6b2267bda8ab --- /dev/null +++ b/drivers/staging/media/imx/imx-ic.h @@ -0,0 +1,38 @@ +/* + * V4L2 Image Converter Subdev for Freescale i.MX5/6 SOC + * + * Copyright (c) 2016 Mentor Graphics Inc. + * + * 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; either version 2 of the License, or + * (at your option) any later version. + */ +#ifndef _IMX_IC_H +#define _IMX_IC_H + +#include <media/v4l2-subdev.h> + +struct imx_ic_priv { + struct device *dev; + struct v4l2_subdev sd; + int ipu_id; + int task_id; + void *prp_priv; + void *task_priv; +}; + +struct imx_ic_ops { + const struct v4l2_subdev_ops *subdev_ops; + const struct v4l2_subdev_internal_ops *internal_ops; + const struct media_entity_operations *entity_ops; + + int (*init)(struct imx_ic_priv *ic_priv); + void (*remove)(struct imx_ic_priv *ic_priv); +}; + +extern struct imx_ic_ops imx_ic_prp_ops; +extern struct imx_ic_ops imx_ic_prpencvf_ops; +extern struct imx_ic_ops imx_ic_pp_ops; + +#endif diff --git a/drivers/staging/media/imx/imx-media-capture.c b/drivers/staging/media/imx/imx-media-capture.c new file mode 100644 index 000000000000..ddab4c249da2 --- /dev/null +++ b/drivers/staging/media/imx/imx-media-capture.c @@ -0,0 +1,775 @@ +/* + * Video Capture Subdev for Freescale i.MX5/6 SOC + * + * Copyright (c) 2012-2016 Mentor Graphics Inc. + * + * 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; either version 2 of the License, or + * (at your option) any later version. + */ +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/pinctrl/consumer.h> +#include <linux/platform_device.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/timer.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-mc.h> +#include <media/v4l2-subdev.h> +#include <media/videobuf2-dma-contig.h> +#include <video/imx-ipu-v3.h> +#include <media/imx.h> +#include "imx-media.h" + +struct capture_priv { + struct imx_media_video_dev vdev; + + struct v4l2_subdev *src_sd; + int src_sd_pad; + struct device *dev; + + struct imx_media_dev *md; + + struct media_pad vdev_pad; + + struct mutex mutex; /* capture device mutex */ + + /* the videobuf2 queue */ + struct vb2_queue q; + /* list of ready imx_media_buffer's from q */ + struct list_head ready_q; + /* protect ready_q */ + spinlock_t q_lock; + + /* controls inherited from subdevs */ + struct v4l2_ctrl_handler ctrl_hdlr; + + /* misc status */ + bool stop; /* streaming is stopping */ +}; + +#define to_capture_priv(v) container_of(v, struct capture_priv, vdev) + +/* In bytes, per queue */ +#define VID_MEM_LIMIT SZ_64M + +static struct vb2_ops capture_qops; + +/* + * Video ioctls follow + */ + +static int vidioc_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + struct capture_priv *priv = video_drvdata(file); + + strncpy(cap->driver, "imx-media-capture", sizeof(cap->driver) - 1); + strncpy(cap->card, "imx-media-capture", sizeof(cap->card) - 1); + snprintf(cap->bus_info, sizeof(cap->bus_info), + "platform:%s", priv->src_sd->name); + + return 0; +} + +static int capture_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + struct capture_priv *priv = video_drvdata(file); + const struct imx_media_pixfmt *cc; + struct v4l2_subdev_frame_size_enum fse = { + .index = fsize->index, + .pad = priv->src_sd_pad, + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + int ret; + + cc = imx_media_find_format(fsize->pixel_format, CS_SEL_ANY, true); + if (!cc) + return -EINVAL; + + fse.code = cc->codes[0]; + + ret = v4l2_subdev_call(priv->src_sd, pad, enum_frame_size, NULL, &fse); + if (ret) + return ret; + + if (fse.min_width == fse.max_width && + fse.min_height == fse.max_height) { + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + fsize->discrete.width = fse.min_width; + fsize->discrete.height = fse.min_height; + } else { + fsize->type = V4L2_FRMSIZE_TYPE_CONTINUOUS; + fsize->stepwise.min_width = fse.min_width; + fsize->stepwise.max_width = fse.max_width; + fsize->stepwise.min_height = fse.min_height; + fsize->stepwise.max_height = fse.max_height; + fsize->stepwise.step_width = 1; + fsize->stepwise.step_height = 1; + } + + return 0; +} + +static int capture_enum_frameintervals(struct file *file, void *fh, + struct v4l2_frmivalenum *fival) +{ + struct capture_priv *priv = video_drvdata(file); + const struct imx_media_pixfmt *cc; + struct v4l2_subdev_frame_interval_enum fie = { + .index = fival->index, + .pad = priv->src_sd_pad, + .width = fival->width, + .height = fival->height, + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + int ret; + + cc = imx_media_find_format(fival->pixel_format, CS_SEL_ANY, true); + if (!cc) + return -EINVAL; + + fie.code = cc->codes[0]; + + ret = v4l2_subdev_call(priv->src_sd, pad, enum_frame_interval, NULL, &fie); + if (ret) + return ret; + + fival->type = V4L2_FRMIVAL_TYPE_DISCRETE; + fival->discrete = fie.interval; + + return 0; +} + +static int capture_enum_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + struct capture_priv *priv = video_drvdata(file); + const struct imx_media_pixfmt *cc_src; + struct v4l2_subdev_format fmt_src; + u32 fourcc; + int ret; + + fmt_src.pad = priv->src_sd_pad; + fmt_src.which = V4L2_SUBDEV_FORMAT_ACTIVE; + ret = v4l2_subdev_call(priv->src_sd, pad, get_fmt, NULL, &fmt_src); + if (ret) { + v4l2_err(priv->src_sd, "failed to get src_sd format\n"); + return ret; + } + + cc_src = imx_media_find_ipu_format(fmt_src.format.code, CS_SEL_ANY); + if (!cc_src) + cc_src = imx_media_find_mbus_format(fmt_src.format.code, + CS_SEL_ANY, true); + if (!cc_src) + return -EINVAL; + + if (cc_src->bayer) { + if (f->index != 0) + return -EINVAL; + fourcc = cc_src->fourcc; + } else { + u32 cs_sel = (cc_src->cs == IPUV3_COLORSPACE_YUV) ? + CS_SEL_YUV : CS_SEL_RGB; + + ret = imx_media_enum_format(&fourcc, f->index, cs_sel); + if (ret) + return ret; + } + + f->pixelformat = fourcc; + + return 0; +} + +static int capture_g_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct capture_priv *priv = video_drvdata(file); + + *f = priv->vdev.fmt; + + return 0; +} + +static int capture_try_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct capture_priv *priv = video_drvdata(file); + struct v4l2_subdev_format fmt_src; + const struct imx_media_pixfmt *cc, *cc_src; + int ret; + + fmt_src.pad = priv->src_sd_pad; + fmt_src.which = V4L2_SUBDEV_FORMAT_ACTIVE; + ret = v4l2_subdev_call(priv->src_sd, pad, get_fmt, NULL, &fmt_src); + if (ret) + return ret; + + cc_src = imx_media_find_ipu_format(fmt_src.format.code, CS_SEL_ANY); + if (!cc_src) + cc_src = imx_media_find_mbus_format(fmt_src.format.code, + CS_SEL_ANY, true); + if (!cc_src) + return -EINVAL; + + if (cc_src->bayer) { + cc = cc_src; + } else { + u32 fourcc, cs_sel; + + cs_sel = (cc_src->cs == IPUV3_COLORSPACE_YUV) ? + CS_SEL_YUV : CS_SEL_RGB; + fourcc = f->fmt.pix.pixelformat; + + cc = imx_media_find_format(fourcc, cs_sel, false); + if (!cc) { + imx_media_enum_format(&fourcc, 0, cs_sel); + cc = imx_media_find_format(fourcc, cs_sel, false); + } + } + + imx_media_mbus_fmt_to_pix_fmt(&f->fmt.pix, &fmt_src.format, cc); + + return 0; +} + +static int capture_s_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct capture_priv *priv = video_drvdata(file); + int ret; + + if (vb2_is_busy(&priv->q)) { + v4l2_err(priv->src_sd, "%s queue busy\n", __func__); + return -EBUSY; + } + + ret = capture_try_fmt_vid_cap(file, priv, f); + if (ret) + return ret; + + priv->vdev.fmt.fmt.pix = f->fmt.pix; + priv->vdev.cc = imx_media_find_format(f->fmt.pix.pixelformat, + CS_SEL_ANY, true); + + return 0; +} + +static int capture_querystd(struct file *file, void *fh, v4l2_std_id *std) +{ + struct capture_priv *priv = video_drvdata(file); + + return v4l2_subdev_call(priv->src_sd, video, querystd, std); +} + +static int capture_g_std(struct file *file, void *fh, v4l2_std_id *std) +{ + struct capture_priv *priv = video_drvdata(file); + + return v4l2_subdev_call(priv->src_sd, video, g_std, std); +} + +static int capture_s_std(struct file *file, void *fh, v4l2_std_id std) +{ + struct capture_priv *priv = video_drvdata(file); + + if (vb2_is_busy(&priv->q)) + return -EBUSY; + + return v4l2_subdev_call(priv->src_sd, video, s_std, std); +} + +static int capture_g_parm(struct file *file, void *fh, + struct v4l2_streamparm *a) +{ + struct capture_priv *priv = video_drvdata(file); + struct v4l2_subdev_frame_interval fi; + int ret; + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + memset(&fi, 0, sizeof(fi)); + fi.pad = priv->src_sd_pad; + ret = v4l2_subdev_call(priv->src_sd, video, g_frame_interval, &fi); + if (ret < 0) + return ret; + + a->parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + a->parm.capture.timeperframe = fi.interval; + + return 0; +} + +static int capture_s_parm(struct file *file, void *fh, + struct v4l2_streamparm *a) +{ + struct capture_priv *priv = video_drvdata(file); + struct v4l2_subdev_frame_interval fi; + int ret; + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + memset(&fi, 0, sizeof(fi)); + fi.pad = priv->src_sd_pad; + fi.interval = a->parm.capture.timeperframe; + ret = v4l2_subdev_call(priv->src_sd, video, s_frame_interval, &fi); + if (ret < 0) + return ret; + + a->parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + a->parm.capture.timeperframe = fi.interval; + + return 0; +} + +static const struct v4l2_ioctl_ops capture_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + + .vidioc_enum_framesizes = capture_enum_framesizes, + .vidioc_enum_frameintervals = capture_enum_frameintervals, + + .vidioc_enum_fmt_vid_cap = capture_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = capture_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = capture_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = capture_s_fmt_vid_cap, + + .vidioc_querystd = capture_querystd, + .vidioc_g_std = capture_g_std, + .vidioc_s_std = capture_s_std, + + .vidioc_g_parm = capture_g_parm, + .vidioc_s_parm = capture_s_parm, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, +}; + +/* + * Queue operations + */ + +static int capture_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, + unsigned int *nplanes, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct capture_priv *priv = vb2_get_drv_priv(vq); + struct v4l2_pix_format *pix = &priv->vdev.fmt.fmt.pix; + unsigned int count = *nbuffers; + + if (vq->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + if (*nplanes) { + if (*nplanes != 1 || sizes[0] < pix->sizeimage) + return -EINVAL; + count += vq->num_buffers; + } + + count = min_t(__u32, VID_MEM_LIMIT / pix->sizeimage, count); + + if (*nplanes) + *nbuffers = (count < vq->num_buffers) ? 0 : + count - vq->num_buffers; + else + *nbuffers = count; + + *nplanes = 1; + sizes[0] = pix->sizeimage; + + return 0; +} + +static int capture_buf_init(struct vb2_buffer *vb) +{ + struct imx_media_buffer *buf = to_imx_media_vb(vb); + + INIT_LIST_HEAD(&buf->list); + + return 0; +} + +static int capture_buf_prepare(struct vb2_buffer *vb) +{ + struct vb2_queue *vq = vb->vb2_queue; + struct capture_priv *priv = vb2_get_drv_priv(vq); + struct v4l2_pix_format *pix = &priv->vdev.fmt.fmt.pix; + + if (vb2_plane_size(vb, 0) < pix->sizeimage) { + v4l2_err(priv->src_sd, + "data will not fit into plane (%lu < %lu)\n", + vb2_plane_size(vb, 0), (long)pix->sizeimage); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, pix->sizeimage); + + return 0; +} + +static void capture_buf_queue(struct vb2_buffer *vb) +{ + struct capture_priv *priv = vb2_get_drv_priv(vb->vb2_queue); + struct imx_media_buffer *buf = to_imx_media_vb(vb); + unsigned long flags; + + spin_lock_irqsave(&priv->q_lock, flags); + + list_add_tail(&buf->list, &priv->ready_q); + + spin_unlock_irqrestore(&priv->q_lock, flags); +} + +static int capture_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct capture_priv *priv = vb2_get_drv_priv(vq); + struct imx_media_buffer *buf, *tmp; + unsigned long flags; + int ret; + + if (vb2_is_streaming(vq)) + return 0; + + ret = imx_media_pipeline_set_stream(priv->md, &priv->src_sd->entity, + true); + if (ret) { + v4l2_err(priv->src_sd, "pipeline start failed with %d\n", ret); + goto return_bufs; + } + + priv->stop = false; + + return 0; + +return_bufs: + spin_lock_irqsave(&priv->q_lock, flags); + list_for_each_entry_safe(buf, tmp, &priv->ready_q, list) { + list_del(&buf->list); + vb2_buffer_done(&buf->vbuf.vb2_buf, VB2_BUF_STATE_QUEUED); + } + spin_unlock_irqrestore(&priv->q_lock, flags); + return ret; +} + +static void capture_stop_streaming(struct vb2_queue *vq) +{ + struct capture_priv *priv = vb2_get_drv_priv(vq); + struct imx_media_buffer *frame; + unsigned long flags; + int ret; + + if (!vb2_is_streaming(vq)) + return; + + spin_lock_irqsave(&priv->q_lock, flags); + priv->stop = true; + spin_unlock_irqrestore(&priv->q_lock, flags); + + ret = imx_media_pipeline_set_stream(priv->md, &priv->src_sd->entity, + false); + if (ret) + v4l2_warn(priv->src_sd, "pipeline stop failed with %d\n", ret); + + /* release all active buffers */ + spin_lock_irqsave(&priv->q_lock, flags); + while (!list_empty(&priv->ready_q)) { + frame = list_entry(priv->ready_q.next, + struct imx_media_buffer, list); + list_del(&frame->list); + vb2_buffer_done(&frame->vbuf.vb2_buf, VB2_BUF_STATE_ERROR); + } + spin_unlock_irqrestore(&priv->q_lock, flags); +} + +static struct vb2_ops capture_qops = { + .queue_setup = capture_queue_setup, + .buf_init = capture_buf_init, + .buf_prepare = capture_buf_prepare, + .buf_queue = capture_buf_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .start_streaming = capture_start_streaming, + .stop_streaming = capture_stop_streaming, +}; + +/* + * File operations + */ +static int capture_open(struct file *file) +{ + struct capture_priv *priv = video_drvdata(file); + struct video_device *vfd = priv->vdev.vfd; + int ret; + + if (mutex_lock_interruptible(&priv->mutex)) + return -ERESTARTSYS; + + ret = v4l2_fh_open(file); + if (ret) + v4l2_err(priv->src_sd, "v4l2_fh_open failed\n"); + + ret = v4l2_pipeline_pm_use(&vfd->entity, 1); + if (ret) + v4l2_fh_release(file); + + mutex_unlock(&priv->mutex); + return ret; +} + +static int capture_release(struct file *file) +{ + struct capture_priv *priv = video_drvdata(file); + struct video_device *vfd = priv->vdev.vfd; + struct vb2_queue *vq = &priv->q; + int ret = 0; + + mutex_lock(&priv->mutex); + + if (file->private_data == vq->owner) { + vb2_queue_release(vq); + vq->owner = NULL; + } + + v4l2_pipeline_pm_use(&vfd->entity, 0); + + v4l2_fh_release(file); + mutex_unlock(&priv->mutex); + return ret; +} + +static const struct v4l2_file_operations capture_fops = { + .owner = THIS_MODULE, + .open = capture_open, + .release = capture_release, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, +}; + +static struct video_device capture_videodev = { + .fops = &capture_fops, + .ioctl_ops = &capture_ioctl_ops, + .minor = -1, + .release = video_device_release, + .vfl_dir = VFL_DIR_RX, + .tvnorms = V4L2_STD_NTSC | V4L2_STD_PAL | V4L2_STD_SECAM, + .device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING, +}; + +void imx_media_capture_device_set_format(struct imx_media_video_dev *vdev, + struct v4l2_pix_format *pix) +{ + struct capture_priv *priv = to_capture_priv(vdev); + + mutex_lock(&priv->mutex); + priv->vdev.fmt.fmt.pix = *pix; + priv->vdev.cc = imx_media_find_format(pix->pixelformat, CS_SEL_ANY, + true); + mutex_unlock(&priv->mutex); +} +EXPORT_SYMBOL_GPL(imx_media_capture_device_set_format); + +struct imx_media_buffer * +imx_media_capture_device_next_buf(struct imx_media_video_dev *vdev) +{ + struct capture_priv *priv = to_capture_priv(vdev); + struct imx_media_buffer *buf = NULL; + unsigned long flags; + + spin_lock_irqsave(&priv->q_lock, flags); + + /* get next queued buffer */ + if (!list_empty(&priv->ready_q)) { + buf = list_entry(priv->ready_q.next, struct imx_media_buffer, + list); + list_del(&buf->list); + } + + spin_unlock_irqrestore(&priv->q_lock, flags); + + return buf; +} +EXPORT_SYMBOL_GPL(imx_media_capture_device_next_buf); + +void imx_media_capture_device_error(struct imx_media_video_dev *vdev) +{ + struct capture_priv *priv = to_capture_priv(vdev); + struct vb2_queue *vq = &priv->q; + unsigned long flags; + + if (!vb2_is_streaming(vq)) + return; + + spin_lock_irqsave(&priv->q_lock, flags); + vb2_queue_error(vq); + spin_unlock_irqrestore(&priv->q_lock, flags); +} +EXPORT_SYMBOL_GPL(imx_media_capture_device_error); + +int imx_media_capture_device_register(struct imx_media_video_dev *vdev) +{ + struct capture_priv *priv = to_capture_priv(vdev); + struct v4l2_subdev *sd = priv->src_sd; + struct video_device *vfd = vdev->vfd; + struct vb2_queue *vq = &priv->q; + struct v4l2_subdev_format fmt_src; + int ret; + + /* get media device */ + priv->md = dev_get_drvdata(sd->v4l2_dev->dev); + + vfd->v4l2_dev = sd->v4l2_dev; + + ret = video_register_device(vfd, VFL_TYPE_GRABBER, -1); + if (ret) { + v4l2_err(sd, "Failed to register video device\n"); + return ret; + } + + vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vq->io_modes = VB2_MMAP | VB2_DMABUF; + vq->drv_priv = priv; + vq->buf_struct_size = sizeof(struct imx_media_buffer); + vq->ops = &capture_qops; + vq->mem_ops = &vb2_dma_contig_memops; + vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + vq->lock = &priv->mutex; + vq->min_buffers_needed = 2; + vq->dev = priv->dev; + + ret = vb2_queue_init(vq); + if (ret) { + v4l2_err(sd, "vb2_queue_init failed\n"); + goto unreg; + } + + INIT_LIST_HEAD(&priv->ready_q); + + priv->vdev_pad.flags = MEDIA_PAD_FL_SINK; + ret = media_entity_pads_init(&vfd->entity, 1, &priv->vdev_pad); + if (ret) { + v4l2_err(sd, "failed to init dev pad\n"); + goto unreg; + } + + /* create the link from the src_sd devnode pad to device node */ + ret = media_create_pad_link(&sd->entity, priv->src_sd_pad, + &vfd->entity, 0, 0); + if (ret) { + v4l2_err(sd, "failed to create link to device node\n"); + goto unreg; + } + + /* setup default format */ + fmt_src.pad = priv->src_sd_pad; + fmt_src.which = V4L2_SUBDEV_FORMAT_ACTIVE; + v4l2_subdev_call(sd, pad, get_fmt, NULL, &fmt_src); + if (ret) { + v4l2_err(sd, "failed to get src_sd format\n"); + goto unreg; + } + + vdev->fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + imx_media_mbus_fmt_to_pix_fmt(&vdev->fmt.fmt.pix, + &fmt_src.format, NULL); + vdev->cc = imx_media_find_format(vdev->fmt.fmt.pix.pixelformat, + CS_SEL_ANY, false); + + v4l2_info(sd, "Registered %s as /dev/%s\n", vfd->name, + video_device_node_name(vfd)); + + vfd->ctrl_handler = &priv->ctrl_hdlr; + + return 0; +unreg: + video_unregister_device(vfd); + return ret; +} +EXPORT_SYMBOL_GPL(imx_media_capture_device_register); + +void imx_media_capture_device_unregister(struct imx_media_video_dev *vdev) +{ + struct capture_priv *priv = to_capture_priv(vdev); + struct video_device *vfd = priv->vdev.vfd; + + mutex_lock(&priv->mutex); + + if (video_is_registered(vfd)) { + video_unregister_device(vfd); + media_entity_cleanup(&vfd->entity); + } + + mutex_unlock(&priv->mutex); +} +EXPORT_SYMBOL_GPL(imx_media_capture_device_unregister); + +struct imx_media_video_dev * +imx_media_capture_device_init(struct v4l2_subdev *src_sd, int pad) +{ + struct capture_priv *priv; + struct video_device *vfd; + + priv = devm_kzalloc(src_sd->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return ERR_PTR(-ENOMEM); + + priv->src_sd = src_sd; + priv->src_sd_pad = pad; + priv->dev = src_sd->dev; + + mutex_init(&priv->mutex); + spin_lock_init(&priv->q_lock); + + snprintf(capture_videodev.name, sizeof(capture_videodev.name), + "%s capture", src_sd->name); + + vfd = video_device_alloc(); + if (!vfd) + return ERR_PTR(-ENOMEM); + + *vfd = capture_videodev; + vfd->lock = &priv->mutex; + vfd->queue = &priv->q; + priv->vdev.vfd = vfd; + + video_set_drvdata(vfd, priv); + + v4l2_ctrl_handler_init(&priv->ctrl_hdlr, 0); + + return &priv->vdev; +} +EXPORT_SYMBOL_GPL(imx_media_capture_device_init); + +void imx_media_capture_device_remove(struct imx_media_video_dev *vdev) +{ + struct capture_priv *priv = to_capture_priv(vdev); + + v4l2_ctrl_handler_free(&priv->ctrl_hdlr); +} +EXPORT_SYMBOL_GPL(imx_media_capture_device_remove); + +MODULE_DESCRIPTION("i.MX5/6 v4l2 video capture interface driver"); +MODULE_AUTHOR("Steve Longerbeam <steve_longerbeam@mentor.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/media/imx/imx-media-csi.c b/drivers/staging/media/imx/imx-media-csi.c new file mode 100644 index 000000000000..a2d26693912e --- /dev/null +++ b/drivers/staging/media/imx/imx-media-csi.c @@ -0,0 +1,1817 @@ +/* + * V4L2 Capture CSI Subdev for Freescale i.MX5/6 SOC + * + * Copyright (c) 2014-2017 Mentor Graphics Inc. + * Copyright (C) 2017 Pengutronix, Philipp Zabel <kernel@pengutronix.de> + * + * 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; either version 2 of the License, or + * (at your option) any later version. + */ +#include <linux/delay.h> +#include <linux/gcd.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/pinctrl/consumer.h> +#include <linux/platform_device.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-mc.h> +#include <media/v4l2-subdev.h> +#include <media/videobuf2-dma-contig.h> +#include <video/imx-ipu-v3.h> +#include <media/imx.h> +#include "imx-media.h" + +/* + * Min/Max supported width and heights. + * + * We allow planar output, so we have to align width by 16 pixels + * to meet IDMAC alignment requirements. + * + * TODO: move this into pad format negotiation, if capture device + * has not requested planar formats, we should allow 8 pixel + * alignment. + */ +#define MIN_W 176 +#define MIN_H 144 +#define MAX_W 4096 +#define MAX_H 4096 +#define W_ALIGN 4 /* multiple of 16 pixels */ +#define H_ALIGN 1 /* multiple of 2 lines */ +#define S_ALIGN 1 /* multiple of 2 */ + +/* + * struct csi_skip_desc - CSI frame skipping descriptor + * @keep - number of frames kept per max_ratio frames + * @max_ratio - width of skip_smfc, written to MAX_RATIO bitfield + * @skip_smfc - skip pattern written to the SKIP_SMFC bitfield + */ +struct csi_skip_desc { + u8 keep; + u8 max_ratio; + u8 skip_smfc; +}; + +struct csi_priv { + struct device *dev; + struct ipu_soc *ipu; + struct imx_media_dev *md; + struct v4l2_subdev sd; + struct media_pad pad[CSI_NUM_PADS]; + /* the video device at IDMAC output pad */ + struct imx_media_video_dev *vdev; + struct imx_media_fim *fim; + int csi_id; + int smfc_id; + + /* lock to protect all members below */ + struct mutex lock; + + int active_output_pad; + + struct ipuv3_channel *idmac_ch; + struct ipu_smfc *smfc; + struct ipu_csi *csi; + + struct v4l2_mbus_framefmt format_mbus[CSI_NUM_PADS]; + const struct imx_media_pixfmt *cc[CSI_NUM_PADS]; + struct v4l2_fract frame_interval[CSI_NUM_PADS]; + struct v4l2_rect crop; + struct v4l2_rect compose; + const struct csi_skip_desc *skip; + + /* active vb2 buffers to send to video dev sink */ + struct imx_media_buffer *active_vb2_buf[2]; + struct imx_media_dma_buf underrun_buf; + + int ipu_buf_num; /* ipu double buffer index: 0-1 */ + + /* the sink for the captured frames */ + struct media_entity *sink; + enum ipu_csi_dest dest; + /* the source subdev */ + struct v4l2_subdev *src_sd; + + /* the mipi virtual channel number at link validate */ + int vc_num; + + /* the attached sensor at stream on */ + struct imx_media_subdev *sensor; + + spinlock_t irqlock; /* protect eof_irq handler */ + struct timer_list eof_timeout_timer; + int eof_irq; + int nfb4eof_irq; + + struct v4l2_ctrl_handler ctrl_hdlr; + + int stream_count; /* streaming counter */ + bool last_eof; /* waiting for last EOF at stream off */ + bool nfb4eof; /* NFB4EOF encountered during streaming */ + struct completion last_eof_comp; +}; + +static inline struct csi_priv *sd_to_dev(struct v4l2_subdev *sdev) +{ + return container_of(sdev, struct csi_priv, sd); +} + +static void csi_idmac_put_ipu_resources(struct csi_priv *priv) +{ + if (!IS_ERR_OR_NULL(priv->idmac_ch)) + ipu_idmac_put(priv->idmac_ch); + priv->idmac_ch = NULL; + + if (!IS_ERR_OR_NULL(priv->smfc)) + ipu_smfc_put(priv->smfc); + priv->smfc = NULL; +} + +static int csi_idmac_get_ipu_resources(struct csi_priv *priv) +{ + int ch_num, ret; + + ch_num = IPUV3_CHANNEL_CSI0 + priv->smfc_id; + + priv->smfc = ipu_smfc_get(priv->ipu, ch_num); + if (IS_ERR(priv->smfc)) { + v4l2_err(&priv->sd, "failed to get SMFC\n"); + ret = PTR_ERR(priv->smfc); + goto out; + } + + priv->idmac_ch = ipu_idmac_get(priv->ipu, ch_num); + if (IS_ERR(priv->idmac_ch)) { + v4l2_err(&priv->sd, "could not get IDMAC channel %u\n", + ch_num); + ret = PTR_ERR(priv->idmac_ch); + goto out; + } + + return 0; +out: + csi_idmac_put_ipu_resources(priv); + return ret; +} + +static void csi_vb2_buf_done(struct csi_priv *priv) +{ + struct imx_media_video_dev *vdev = priv->vdev; + struct imx_media_buffer *done, *next; + struct vb2_buffer *vb; + dma_addr_t phys; + + done = priv->active_vb2_buf[priv->ipu_buf_num]; + if (done) { + vb = &done->vbuf.vb2_buf; + vb->timestamp = ktime_get_ns(); + vb2_buffer_done(vb, priv->nfb4eof ? + VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE); + } + + priv->nfb4eof = false; + + /* get next queued buffer */ + next = imx_media_capture_device_next_buf(vdev); + if (next) { + phys = vb2_dma_contig_plane_dma_addr(&next->vbuf.vb2_buf, 0); + priv->active_vb2_buf[priv->ipu_buf_num] = next; + } else { + phys = priv->underrun_buf.phys; + priv->active_vb2_buf[priv->ipu_buf_num] = NULL; + } + + if (ipu_idmac_buffer_is_ready(priv->idmac_ch, priv->ipu_buf_num)) + ipu_idmac_clear_buffer(priv->idmac_ch, priv->ipu_buf_num); + + ipu_cpmem_set_buffer(priv->idmac_ch, priv->ipu_buf_num, phys); +} + +static irqreturn_t csi_idmac_eof_interrupt(int irq, void *dev_id) +{ + struct csi_priv *priv = dev_id; + + spin_lock(&priv->irqlock); + + if (priv->last_eof) { + complete(&priv->last_eof_comp); + priv->last_eof = false; + goto unlock; + } + + if (priv->fim) { + struct timespec cur_ts; + + ktime_get_ts(&cur_ts); + /* call frame interval monitor */ + imx_media_fim_eof_monitor(priv->fim, &cur_ts); + } + + csi_vb2_buf_done(priv); + + /* select new IPU buf */ + ipu_idmac_select_buffer(priv->idmac_ch, priv->ipu_buf_num); + /* toggle IPU double-buffer index */ + priv->ipu_buf_num ^= 1; + + /* bump the EOF timeout timer */ + mod_timer(&priv->eof_timeout_timer, + jiffies + msecs_to_jiffies(IMX_MEDIA_EOF_TIMEOUT)); + +unlock: + spin_unlock(&priv->irqlock); + return IRQ_HANDLED; +} + +static irqreturn_t csi_idmac_nfb4eof_interrupt(int irq, void *dev_id) +{ + struct csi_priv *priv = dev_id; + + spin_lock(&priv->irqlock); + + /* + * this is not an unrecoverable error, just mark + * the next captured frame with vb2 error flag. + */ + priv->nfb4eof = true; + + v4l2_err(&priv->sd, "NFB4EOF\n"); + + spin_unlock(&priv->irqlock); + + return IRQ_HANDLED; +} + +/* + * EOF timeout timer function. This is an unrecoverable condition + * without a stream restart. + */ +static void csi_idmac_eof_timeout(unsigned long data) +{ + struct csi_priv *priv = (struct csi_priv *)data; + struct imx_media_video_dev *vdev = priv->vdev; + + v4l2_err(&priv->sd, "EOF timeout\n"); + + /* signal a fatal error to capture device */ + imx_media_capture_device_error(vdev); +} + +static void csi_idmac_setup_vb2_buf(struct csi_priv *priv, dma_addr_t *phys) +{ + struct imx_media_video_dev *vdev = priv->vdev; + struct imx_media_buffer *buf; + int i; + + for (i = 0; i < 2; i++) { + buf = imx_media_capture_device_next_buf(vdev); + if (buf) { + priv->active_vb2_buf[i] = buf; + phys[i] = vb2_dma_contig_plane_dma_addr( + &buf->vbuf.vb2_buf, 0); + } else { + priv->active_vb2_buf[i] = NULL; + phys[i] = priv->underrun_buf.phys; + } + } +} + +static void csi_idmac_unsetup_vb2_buf(struct csi_priv *priv, + enum vb2_buffer_state return_status) +{ + struct imx_media_buffer *buf; + int i; + + /* return any remaining active frames with return_status */ + for (i = 0; i < 2; i++) { + buf = priv->active_vb2_buf[i]; + if (buf) { + struct vb2_buffer *vb = &buf->vbuf.vb2_buf; + + vb->timestamp = ktime_get_ns(); + vb2_buffer_done(vb, return_status); + } + } +} + +/* init the SMFC IDMAC channel */ +static int csi_idmac_setup_channel(struct csi_priv *priv) +{ + struct imx_media_video_dev *vdev = priv->vdev; + struct v4l2_fwnode_endpoint *sensor_ep; + struct v4l2_mbus_framefmt *infmt; + struct ipu_image image; + u32 passthrough_bits; + dma_addr_t phys[2]; + bool passthrough; + u32 burst_size; + int ret; + + infmt = &priv->format_mbus[CSI_SINK_PAD]; + sensor_ep = &priv->sensor->sensor_ep; + + ipu_cpmem_zero(priv->idmac_ch); + + memset(&image, 0, sizeof(image)); + image.pix = vdev->fmt.fmt.pix; + image.rect.width = image.pix.width; + image.rect.height = image.pix.height; + + csi_idmac_setup_vb2_buf(priv, phys); + + image.phys0 = phys[0]; + image.phys1 = phys[1]; + + /* + * Check for conditions that require the IPU to handle the + * data internally as generic data, aka passthrough mode: + * - raw bayer formats + * - the sensor bus is 16-bit parallel + */ + switch (image.pix.pixelformat) { + case V4L2_PIX_FMT_SBGGR8: + case V4L2_PIX_FMT_SGBRG8: + case V4L2_PIX_FMT_SGRBG8: + case V4L2_PIX_FMT_SRGGB8: + burst_size = 8; + passthrough = true; + passthrough_bits = 8; + break; + case V4L2_PIX_FMT_SBGGR16: + case V4L2_PIX_FMT_SGBRG16: + case V4L2_PIX_FMT_SGRBG16: + case V4L2_PIX_FMT_SRGGB16: + burst_size = 4; + passthrough = true; + passthrough_bits = 16; + break; + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_NV12: + burst_size = (image.pix.width & 0x3f) ? + ((image.pix.width & 0x1f) ? + ((image.pix.width & 0xf) ? 8 : 16) : 32) : 64; + passthrough = (sensor_ep->bus_type != V4L2_MBUS_CSI2 && + sensor_ep->bus.parallel.bus_width >= 16); + passthrough_bits = 16; + break; + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_UYVY: + burst_size = (image.pix.width & 0x1f) ? + ((image.pix.width & 0xf) ? 8 : 16) : 32; + passthrough = (sensor_ep->bus_type != V4L2_MBUS_CSI2 && + sensor_ep->bus.parallel.bus_width >= 16); + passthrough_bits = 16; + break; + default: + burst_size = (image.pix.width & 0xf) ? 8 : 16; + passthrough = (sensor_ep->bus_type != V4L2_MBUS_CSI2 && + sensor_ep->bus.parallel.bus_width >= 16); + passthrough_bits = 16; + break; + } + + if (passthrough) { + ipu_cpmem_set_resolution(priv->idmac_ch, image.rect.width, + image.rect.height); + ipu_cpmem_set_stride(priv->idmac_ch, image.pix.bytesperline); + ipu_cpmem_set_buffer(priv->idmac_ch, 0, image.phys0); + ipu_cpmem_set_buffer(priv->idmac_ch, 1, image.phys1); + ipu_cpmem_set_format_passthrough(priv->idmac_ch, + passthrough_bits); + } else { + ret = ipu_cpmem_set_image(priv->idmac_ch, &image); + if (ret) + goto unsetup_vb2; + } + + ipu_cpmem_set_burstsize(priv->idmac_ch, burst_size); + + /* + * Set the channel for the direct CSI-->memory via SMFC + * use-case to very high priority, by enabling the watermark + * signal in the SMFC, enabling WM in the channel, and setting + * the channel priority to high. + * + * Refer to the i.mx6 rev. D TRM Table 36-8: Calculated priority + * value. + * + * The WM's are set very low by intention here to ensure that + * the SMFC FIFOs do not overflow. + */ + ipu_smfc_set_watermark(priv->smfc, 0x02, 0x01); + ipu_cpmem_set_high_priority(priv->idmac_ch); + ipu_idmac_enable_watermark(priv->idmac_ch, true); + ipu_cpmem_set_axi_id(priv->idmac_ch, 0); + + burst_size = passthrough ? + (burst_size >> 3) - 1 : (burst_size >> 2) - 1; + + ipu_smfc_set_burstsize(priv->smfc, burst_size); + + if (image.pix.field == V4L2_FIELD_NONE && + V4L2_FIELD_HAS_BOTH(infmt->field)) + ipu_cpmem_interlaced_scan(priv->idmac_ch, + image.pix.bytesperline); + + ipu_idmac_set_double_buffer(priv->idmac_ch, true); + + return 0; + +unsetup_vb2: + csi_idmac_unsetup_vb2_buf(priv, VB2_BUF_STATE_QUEUED); + return ret; +} + +static void csi_idmac_unsetup(struct csi_priv *priv, + enum vb2_buffer_state state) +{ + ipu_idmac_disable_channel(priv->idmac_ch); + ipu_smfc_disable(priv->smfc); + + csi_idmac_unsetup_vb2_buf(priv, state); +} + +static int csi_idmac_setup(struct csi_priv *priv) +{ + int ret; + + ret = csi_idmac_setup_channel(priv); + if (ret) + return ret; + + ipu_cpmem_dump(priv->idmac_ch); + ipu_dump(priv->ipu); + + ipu_smfc_enable(priv->smfc); + + /* set buffers ready */ + ipu_idmac_select_buffer(priv->idmac_ch, 0); + ipu_idmac_select_buffer(priv->idmac_ch, 1); + + /* enable the channels */ + ipu_idmac_enable_channel(priv->idmac_ch); + + return 0; +} + +static int csi_idmac_start(struct csi_priv *priv) +{ + struct imx_media_video_dev *vdev = priv->vdev; + struct v4l2_pix_format *outfmt; + int ret; + + ret = csi_idmac_get_ipu_resources(priv); + if (ret) + return ret; + + ipu_smfc_map_channel(priv->smfc, priv->csi_id, priv->vc_num); + + outfmt = &vdev->fmt.fmt.pix; + + ret = imx_media_alloc_dma_buf(priv->md, &priv->underrun_buf, + outfmt->sizeimage); + if (ret) + goto out_put_ipu; + + priv->ipu_buf_num = 0; + + /* init EOF completion waitq */ + init_completion(&priv->last_eof_comp); + priv->last_eof = false; + priv->nfb4eof = false; + + ret = csi_idmac_setup(priv); + if (ret) { + v4l2_err(&priv->sd, "csi_idmac_setup failed: %d\n", ret); + goto out_free_dma_buf; + } + + priv->nfb4eof_irq = ipu_idmac_channel_irq(priv->ipu, + priv->idmac_ch, + IPU_IRQ_NFB4EOF); + ret = devm_request_irq(priv->dev, priv->nfb4eof_irq, + csi_idmac_nfb4eof_interrupt, 0, + "imx-smfc-nfb4eof", priv); + if (ret) { + v4l2_err(&priv->sd, + "Error registering NFB4EOF irq: %d\n", ret); + goto out_unsetup; + } + + priv->eof_irq = ipu_idmac_channel_irq(priv->ipu, priv->idmac_ch, + IPU_IRQ_EOF); + + ret = devm_request_irq(priv->dev, priv->eof_irq, + csi_idmac_eof_interrupt, 0, + "imx-smfc-eof", priv); + if (ret) { + v4l2_err(&priv->sd, + "Error registering eof irq: %d\n", ret); + goto out_free_nfb4eof_irq; + } + + /* start the EOF timeout timer */ + mod_timer(&priv->eof_timeout_timer, + jiffies + msecs_to_jiffies(IMX_MEDIA_EOF_TIMEOUT)); + + return 0; + +out_free_nfb4eof_irq: + devm_free_irq(priv->dev, priv->nfb4eof_irq, priv); +out_unsetup: + csi_idmac_unsetup(priv, VB2_BUF_STATE_QUEUED); +out_free_dma_buf: + imx_media_free_dma_buf(priv->md, &priv->underrun_buf); +out_put_ipu: + csi_idmac_put_ipu_resources(priv); + return ret; +} + +static void csi_idmac_stop(struct csi_priv *priv) +{ + unsigned long flags; + int ret; + + /* mark next EOF interrupt as the last before stream off */ + spin_lock_irqsave(&priv->irqlock, flags); + priv->last_eof = true; + spin_unlock_irqrestore(&priv->irqlock, flags); + + /* + * and then wait for interrupt handler to mark completion. + */ + ret = wait_for_completion_timeout( + &priv->last_eof_comp, msecs_to_jiffies(IMX_MEDIA_EOF_TIMEOUT)); + if (ret == 0) + v4l2_warn(&priv->sd, "wait last EOF timeout\n"); + + devm_free_irq(priv->dev, priv->eof_irq, priv); + devm_free_irq(priv->dev, priv->nfb4eof_irq, priv); + + csi_idmac_unsetup(priv, VB2_BUF_STATE_ERROR); + + imx_media_free_dma_buf(priv->md, &priv->underrun_buf); + + /* cancel the EOF timeout timer */ + del_timer_sync(&priv->eof_timeout_timer); + + csi_idmac_put_ipu_resources(priv); +} + +/* Update the CSI whole sensor and active windows */ +static int csi_setup(struct csi_priv *priv) +{ + struct v4l2_mbus_framefmt *infmt, *outfmt; + struct v4l2_mbus_config sensor_mbus_cfg; + struct v4l2_fwnode_endpoint *sensor_ep; + struct v4l2_mbus_framefmt if_fmt; + + infmt = &priv->format_mbus[CSI_SINK_PAD]; + outfmt = &priv->format_mbus[priv->active_output_pad]; + sensor_ep = &priv->sensor->sensor_ep; + + /* compose mbus_config from sensor endpoint */ + sensor_mbus_cfg.type = sensor_ep->bus_type; + sensor_mbus_cfg.flags = (sensor_ep->bus_type == V4L2_MBUS_CSI2) ? + sensor_ep->bus.mipi_csi2.flags : + sensor_ep->bus.parallel.flags; + + /* + * we need to pass input sensor frame to CSI interface, but + * with translated field type from output format + */ + if_fmt = *infmt; + if_fmt.field = outfmt->field; + + ipu_csi_set_window(priv->csi, &priv->crop); + + ipu_csi_set_downsize(priv->csi, + priv->crop.width == 2 * priv->compose.width, + priv->crop.height == 2 * priv->compose.height); + + ipu_csi_init_interface(priv->csi, &sensor_mbus_cfg, &if_fmt); + + ipu_csi_set_dest(priv->csi, priv->dest); + + if (priv->dest == IPU_CSI_DEST_IDMAC) + ipu_csi_set_skip_smfc(priv->csi, priv->skip->skip_smfc, + priv->skip->max_ratio - 1, 0); + + ipu_csi_dump(priv->csi); + + return 0; +} + +static int csi_start(struct csi_priv *priv) +{ + struct v4l2_fract *output_fi, *input_fi; + u32 bad_frames = 0; + int ret; + + if (!priv->sensor) { + v4l2_err(&priv->sd, "no sensor attached\n"); + return -EINVAL; + } + + output_fi = &priv->frame_interval[priv->active_output_pad]; + input_fi = &priv->frame_interval[CSI_SINK_PAD]; + + ret = v4l2_subdev_call(priv->sensor->sd, sensor, + g_skip_frames, &bad_frames); + if (!ret && bad_frames) { + u32 delay_usec; + + /* + * This sensor has bad frames when it is turned on, + * add a delay to avoid them before enabling the CSI + * hardware. Especially for sensors with a bt.656 interface, + * any shifts in the SAV/EAV sync codes will cause the CSI + * to lose vert/horiz sync. + */ + delay_usec = DIV_ROUND_UP_ULL( + (u64)USEC_PER_SEC * input_fi->numerator * bad_frames, + input_fi->denominator); + usleep_range(delay_usec, delay_usec + 1000); + } + + if (priv->dest == IPU_CSI_DEST_IDMAC) { + ret = csi_idmac_start(priv); + if (ret) + return ret; + } + + ret = csi_setup(priv); + if (ret) + goto idmac_stop; + + /* start the frame interval monitor */ + if (priv->fim && priv->dest == IPU_CSI_DEST_IDMAC) { + ret = imx_media_fim_set_stream(priv->fim, output_fi, true); + if (ret) + goto idmac_stop; + } + + ret = ipu_csi_enable(priv->csi); + if (ret) { + v4l2_err(&priv->sd, "CSI enable error: %d\n", ret); + goto fim_off; + } + + return 0; + +fim_off: + if (priv->fim && priv->dest == IPU_CSI_DEST_IDMAC) + imx_media_fim_set_stream(priv->fim, NULL, false); +idmac_stop: + if (priv->dest == IPU_CSI_DEST_IDMAC) + csi_idmac_stop(priv); + return ret; +} + +static void csi_stop(struct csi_priv *priv) +{ + if (priv->dest == IPU_CSI_DEST_IDMAC) { + csi_idmac_stop(priv); + + /* stop the frame interval monitor */ + if (priv->fim) + imx_media_fim_set_stream(priv->fim, NULL, false); + } + + ipu_csi_disable(priv->csi); +} + +static const struct csi_skip_desc csi_skip[12] = { + { 1, 1, 0x00 }, /* Keep all frames */ + { 5, 6, 0x10 }, /* Skip every sixth frame */ + { 4, 5, 0x08 }, /* Skip every fifth frame */ + { 3, 4, 0x04 }, /* Skip every fourth frame */ + { 2, 3, 0x02 }, /* Skip every third frame */ + { 3, 5, 0x0a }, /* Skip frames 1 and 3 of every 5 */ + { 1, 2, 0x01 }, /* Skip every second frame */ + { 2, 5, 0x0b }, /* Keep frames 1 and 4 of every 5 */ + { 1, 3, 0x03 }, /* Keep one in three frames */ + { 1, 4, 0x07 }, /* Keep one in four frames */ + { 1, 5, 0x0f }, /* Keep one in five frames */ + { 1, 6, 0x1f }, /* Keep one in six frames */ +}; + +static void csi_apply_skip_interval(const struct csi_skip_desc *skip, + struct v4l2_fract *interval) +{ + unsigned int div; + + interval->numerator *= skip->max_ratio; + interval->denominator *= skip->keep; + + /* Reduce fraction to lowest terms */ + div = gcd(interval->numerator, interval->denominator); + if (div > 1) { + interval->numerator /= div; + interval->denominator /= div; + } +} + +/* + * Find the skip pattern to produce the output frame interval closest to the + * requested one, for the given input frame interval. Updates the output frame + * interval to the exact value. + */ +static const struct csi_skip_desc *csi_find_best_skip(struct v4l2_fract *in, + struct v4l2_fract *out) +{ + const struct csi_skip_desc *skip = &csi_skip[0], *best_skip = skip; + u32 min_err = UINT_MAX; + u64 want_us; + int i; + + /* Default to 1:1 ratio */ + if (out->numerator == 0 || out->denominator == 0 || + in->numerator == 0 || in->denominator == 0) { + *out = *in; + return best_skip; + } + + want_us = div_u64((u64)USEC_PER_SEC * out->numerator, out->denominator); + + /* Find the reduction closest to the requested time per frame */ + for (i = 0; i < ARRAY_SIZE(csi_skip); i++, skip++) { + u64 tmp, err; + + tmp = div_u64((u64)USEC_PER_SEC * in->numerator * + skip->max_ratio, in->denominator * skip->keep); + + err = abs((s64)tmp - want_us); + if (err < min_err) { + min_err = err; + best_skip = skip; + } + } + + *out = *in; + csi_apply_skip_interval(best_skip, out); + + return best_skip; +} + +/* + * V4L2 subdev operations. + */ + +static int csi_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct csi_priv *priv = v4l2_get_subdevdata(sd); + + if (fi->pad >= CSI_NUM_PADS) + return -EINVAL; + + mutex_lock(&priv->lock); + + fi->interval = priv->frame_interval[fi->pad]; + + mutex_unlock(&priv->lock); + + return 0; +} + +static int csi_s_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct csi_priv *priv = v4l2_get_subdevdata(sd); + struct v4l2_fract *input_fi; + int ret = 0; + + mutex_lock(&priv->lock); + + input_fi = &priv->frame_interval[CSI_SINK_PAD]; + + switch (fi->pad) { + case CSI_SINK_PAD: + /* No limits on input frame interval */ + /* Reset output intervals and frame skipping ratio to 1:1 */ + priv->frame_interval[CSI_SRC_PAD_IDMAC] = fi->interval; + priv->frame_interval[CSI_SRC_PAD_DIRECT] = fi->interval; + priv->skip = &csi_skip[0]; + break; + case CSI_SRC_PAD_IDMAC: + /* + * frame interval at IDMAC output pad depends on input + * interval, modified by frame skipping. + */ + priv->skip = csi_find_best_skip(input_fi, &fi->interval); + break; + case CSI_SRC_PAD_DIRECT: + /* + * frame interval at DIRECT output pad is same as input + * interval. + */ + fi->interval = *input_fi; + break; + default: + ret = -EINVAL; + goto out; + } + + priv->frame_interval[fi->pad] = fi->interval; +out: + mutex_unlock(&priv->lock); + return ret; +} + +static int csi_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct csi_priv *priv = v4l2_get_subdevdata(sd); + int ret = 0; + + mutex_lock(&priv->lock); + + if (!priv->src_sd || !priv->sink) { + ret = -EPIPE; + goto out; + } + + /* + * enable/disable streaming only if stream_count is + * going from 0 to 1 / 1 to 0. + */ + if (priv->stream_count != !enable) + goto update_count; + + if (enable) { + /* upstream must be started first, before starting CSI */ + ret = v4l2_subdev_call(priv->src_sd, video, s_stream, 1); + ret = (ret && ret != -ENOIOCTLCMD) ? ret : 0; + if (ret) + goto out; + + dev_dbg(priv->dev, "stream ON\n"); + ret = csi_start(priv); + if (ret) { + v4l2_subdev_call(priv->src_sd, video, s_stream, 0); + goto out; + } + } else { + dev_dbg(priv->dev, "stream OFF\n"); + /* CSI must be stopped first, then stop upstream */ + csi_stop(priv); + v4l2_subdev_call(priv->src_sd, video, s_stream, 0); + } + +update_count: + priv->stream_count += enable ? 1 : -1; + if (priv->stream_count < 0) + priv->stream_count = 0; +out: + mutex_unlock(&priv->lock); + return ret; +} + +static int csi_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); + struct csi_priv *priv = v4l2_get_subdevdata(sd); + struct v4l2_subdev *remote_sd; + int ret = 0; + + dev_dbg(priv->dev, "link setup %s -> %s\n", remote->entity->name, + local->entity->name); + + mutex_lock(&priv->lock); + + if (local->flags & MEDIA_PAD_FL_SINK) { + if (!is_media_entity_v4l2_subdev(remote->entity)) { + ret = -EINVAL; + goto out; + } + + remote_sd = media_entity_to_v4l2_subdev(remote->entity); + + if (flags & MEDIA_LNK_FL_ENABLED) { + if (priv->src_sd) { + ret = -EBUSY; + goto out; + } + priv->src_sd = remote_sd; + } else { + priv->src_sd = NULL; + } + + goto out; + } + + /* this is a source pad */ + + if (flags & MEDIA_LNK_FL_ENABLED) { + if (priv->sink) { + ret = -EBUSY; + goto out; + } + } else { + v4l2_ctrl_handler_free(&priv->ctrl_hdlr); + v4l2_ctrl_handler_init(&priv->ctrl_hdlr, 0); + priv->sink = NULL; + goto out; + } + + /* record which output pad is now active */ + priv->active_output_pad = local->index; + + /* set CSI destination */ + if (local->index == CSI_SRC_PAD_IDMAC) { + if (!is_media_entity_v4l2_video_device(remote->entity)) { + ret = -EINVAL; + goto out; + } + + if (priv->fim) { + ret = imx_media_fim_add_controls(priv->fim); + if (ret) + goto out; + } + + priv->dest = IPU_CSI_DEST_IDMAC; + } else { + if (!is_media_entity_v4l2_subdev(remote->entity)) { + ret = -EINVAL; + goto out; + } + + remote_sd = media_entity_to_v4l2_subdev(remote->entity); + switch (remote_sd->grp_id) { + case IMX_MEDIA_GRP_ID_VDIC: + priv->dest = IPU_CSI_DEST_VDIC; + break; + case IMX_MEDIA_GRP_ID_IC_PRP: + priv->dest = IPU_CSI_DEST_IC; + break; + default: + ret = -EINVAL; + goto out; + } + } + + priv->sink = remote->entity; +out: + mutex_unlock(&priv->lock); + return ret; +} + +static int csi_link_validate(struct v4l2_subdev *sd, + struct media_link *link, + struct v4l2_subdev_format *source_fmt, + struct v4l2_subdev_format *sink_fmt) +{ + struct csi_priv *priv = v4l2_get_subdevdata(sd); + struct v4l2_fwnode_endpoint *sensor_ep; + const struct imx_media_pixfmt *incc; + struct imx_media_subdev *sensor; + bool is_csi2; + int ret; + + ret = v4l2_subdev_link_validate_default(sd, link, + source_fmt, sink_fmt); + if (ret) + return ret; + + sensor = __imx_media_find_sensor(priv->md, &priv->sd.entity); + if (IS_ERR(sensor)) { + v4l2_err(&priv->sd, "no sensor attached\n"); + return PTR_ERR(priv->sensor); + } + + mutex_lock(&priv->lock); + + priv->sensor = sensor; + sensor_ep = &priv->sensor->sensor_ep; + is_csi2 = (sensor_ep->bus_type == V4L2_MBUS_CSI2); + incc = priv->cc[CSI_SINK_PAD]; + + if (priv->dest != IPU_CSI_DEST_IDMAC && + (incc->bayer || (!is_csi2 && + sensor_ep->bus.parallel.bus_width >= 16))) { + v4l2_err(&priv->sd, + "bayer/16-bit parallel buses must go to IDMAC pad\n"); + ret = -EINVAL; + goto out; + } + + if (is_csi2) { + int vc_num = 0; + /* + * NOTE! It seems the virtual channels from the mipi csi-2 + * receiver are used only for routing by the video mux's, + * or for hard-wired routing to the CSI's. Once the stream + * enters the CSI's however, they are treated internally + * in the IPU as virtual channel 0. + */ +#if 0 + mutex_unlock(&priv->lock); + vc_num = imx_media_find_mipi_csi2_channel(priv->md, + &priv->sd.entity); + if (vc_num < 0) + return vc_num; + mutex_lock(&priv->lock); +#endif + ipu_csi_set_mipi_datatype(priv->csi, vc_num, + &priv->format_mbus[CSI_SINK_PAD]); + } + + /* select either parallel or MIPI-CSI2 as input to CSI */ + ipu_set_csi_src_mux(priv->ipu, priv->csi_id, is_csi2); +out: + mutex_unlock(&priv->lock); + return ret; +} + +static struct v4l2_mbus_framefmt * +__csi_get_fmt(struct csi_priv *priv, struct v4l2_subdev_pad_config *cfg, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(&priv->sd, cfg, pad); + else + return &priv->format_mbus[pad]; +} + +static struct v4l2_rect * +__csi_get_crop(struct csi_priv *priv, struct v4l2_subdev_pad_config *cfg, + enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_crop(&priv->sd, cfg, CSI_SINK_PAD); + else + return &priv->crop; +} + +static struct v4l2_rect * +__csi_get_compose(struct csi_priv *priv, struct v4l2_subdev_pad_config *cfg, + enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_compose(&priv->sd, cfg, + CSI_SINK_PAD); + else + return &priv->compose; +} + +static void csi_try_crop(struct csi_priv *priv, + struct v4l2_rect *crop, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_mbus_framefmt *infmt, + struct imx_media_subdev *sensor) +{ + struct v4l2_fwnode_endpoint *sensor_ep; + + sensor_ep = &sensor->sensor_ep; + + crop->width = min_t(__u32, infmt->width, crop->width); + if (crop->left + crop->width > infmt->width) + crop->left = infmt->width - crop->width; + /* adjust crop left/width to h/w alignment restrictions */ + crop->left &= ~0x3; + crop->width &= ~0x7; + + /* + * FIXME: not sure why yet, but on interlaced bt.656, + * changing the vertical cropping causes loss of vertical + * sync, so fix it to NTSC/PAL active lines. NTSC contains + * 2 extra lines of active video that need to be cropped. + */ + if (sensor_ep->bus_type == V4L2_MBUS_BT656 && + (V4L2_FIELD_HAS_BOTH(infmt->field) || + infmt->field == V4L2_FIELD_ALTERNATE)) { + crop->height = infmt->height; + crop->top = (infmt->height == 480) ? 2 : 0; + } else { + crop->height = min_t(__u32, infmt->height, crop->height); + if (crop->top + crop->height > infmt->height) + crop->top = infmt->height - crop->height; + } +} + +static int csi_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct csi_priv *priv = v4l2_get_subdevdata(sd); + const struct imx_media_pixfmt *incc; + struct v4l2_mbus_framefmt *infmt; + int ret = 0; + + mutex_lock(&priv->lock); + + infmt = __csi_get_fmt(priv, cfg, CSI_SINK_PAD, code->which); + incc = imx_media_find_mbus_format(infmt->code, CS_SEL_ANY, true); + + switch (code->pad) { + case CSI_SINK_PAD: + ret = imx_media_enum_mbus_format(&code->code, code->index, + CS_SEL_ANY, true); + break; + case CSI_SRC_PAD_DIRECT: + case CSI_SRC_PAD_IDMAC: + if (incc->bayer) { + if (code->index != 0) { + ret = -EINVAL; + goto out; + } + code->code = infmt->code; + } else { + u32 cs_sel = (incc->cs == IPUV3_COLORSPACE_YUV) ? + CS_SEL_YUV : CS_SEL_RGB; + ret = imx_media_enum_ipu_format(&code->code, + code->index, + cs_sel); + } + break; + default: + ret = -EINVAL; + } + +out: + mutex_unlock(&priv->lock); + return ret; +} + +static int csi_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct csi_priv *priv = v4l2_get_subdevdata(sd); + struct v4l2_rect *crop; + int ret = 0; + + if (fse->pad >= CSI_NUM_PADS || + fse->index > (fse->pad == CSI_SINK_PAD ? 0 : 3)) + return -EINVAL; + + mutex_lock(&priv->lock); + + if (fse->pad == CSI_SINK_PAD) { + fse->min_width = MIN_W; + fse->max_width = MAX_W; + fse->min_height = MIN_H; + fse->max_height = MAX_H; + } else { + crop = __csi_get_crop(priv, cfg, fse->which); + + fse->min_width = fse->max_width = fse->index & 1 ? + crop->width / 2 : crop->width; + fse->min_height = fse->max_height = fse->index & 2 ? + crop->height / 2 : crop->height; + } + + mutex_unlock(&priv->lock); + return ret; +} + +static int csi_enum_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_interval_enum *fie) +{ + struct csi_priv *priv = v4l2_get_subdevdata(sd); + struct v4l2_fract *input_fi; + struct v4l2_rect *crop; + int ret = 0; + + if (fie->pad >= CSI_NUM_PADS || + fie->index >= (fie->pad != CSI_SRC_PAD_IDMAC ? + 1 : ARRAY_SIZE(csi_skip))) + return -EINVAL; + + mutex_lock(&priv->lock); + + input_fi = &priv->frame_interval[CSI_SINK_PAD]; + crop = __csi_get_crop(priv, cfg, fie->which); + + if ((fie->width != crop->width && fie->width != crop->width / 2) || + (fie->height != crop->height && fie->height != crop->height / 2)) { + ret = -EINVAL; + goto out; + } + + fie->interval = *input_fi; + + if (fie->pad == CSI_SRC_PAD_IDMAC) + csi_apply_skip_interval(&csi_skip[fie->index], + &fie->interval); + +out: + mutex_unlock(&priv->lock); + return ret; +} + +static int csi_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *sdformat) +{ + struct csi_priv *priv = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *fmt; + int ret = 0; + + if (sdformat->pad >= CSI_NUM_PADS) + return -EINVAL; + + mutex_lock(&priv->lock); + + fmt = __csi_get_fmt(priv, cfg, sdformat->pad, sdformat->which); + if (!fmt) { + ret = -EINVAL; + goto out; + } + + sdformat->format = *fmt; +out: + mutex_unlock(&priv->lock); + return ret; +} + +static void csi_try_fmt(struct csi_priv *priv, + struct imx_media_subdev *sensor, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *sdformat, + struct v4l2_rect *crop, + struct v4l2_rect *compose, + const struct imx_media_pixfmt **cc) +{ + const struct imx_media_pixfmt *incc; + struct v4l2_mbus_framefmt *infmt; + u32 code; + + infmt = __csi_get_fmt(priv, cfg, CSI_SINK_PAD, sdformat->which); + + switch (sdformat->pad) { + case CSI_SRC_PAD_DIRECT: + case CSI_SRC_PAD_IDMAC: + incc = imx_media_find_mbus_format(infmt->code, + CS_SEL_ANY, true); + + sdformat->format.width = compose->width; + sdformat->format.height = compose->height; + + if (incc->bayer) { + sdformat->format.code = infmt->code; + *cc = incc; + } else { + u32 cs_sel = (incc->cs == IPUV3_COLORSPACE_YUV) ? + CS_SEL_YUV : CS_SEL_RGB; + + *cc = imx_media_find_ipu_format(sdformat->format.code, + cs_sel); + if (!*cc) { + imx_media_enum_ipu_format(&code, 0, cs_sel); + *cc = imx_media_find_ipu_format(code, cs_sel); + sdformat->format.code = (*cc)->codes[0]; + } + } + + if (sdformat->pad == CSI_SRC_PAD_DIRECT || + sdformat->format.field != V4L2_FIELD_NONE) + sdformat->format.field = infmt->field; + + /* + * translate V4L2_FIELD_ALTERNATE to SEQ_TB or SEQ_BT + * depending on input height (assume NTSC top-bottom + * order if 480 lines, otherwise PAL bottom-top order). + */ + if (sdformat->format.field == V4L2_FIELD_ALTERNATE) { + sdformat->format.field = (infmt->height == 480) ? + V4L2_FIELD_SEQ_TB : V4L2_FIELD_SEQ_BT; + } + + /* propagate colorimetry from sink */ + sdformat->format.colorspace = infmt->colorspace; + sdformat->format.xfer_func = infmt->xfer_func; + sdformat->format.quantization = infmt->quantization; + sdformat->format.ycbcr_enc = infmt->ycbcr_enc; + break; + case CSI_SINK_PAD: + v4l_bound_align_image(&sdformat->format.width, MIN_W, MAX_W, + W_ALIGN, &sdformat->format.height, + MIN_H, MAX_H, H_ALIGN, S_ALIGN); + + /* Reset crop and compose rectangles */ + crop->left = 0; + crop->top = 0; + crop->width = sdformat->format.width; + crop->height = sdformat->format.height; + csi_try_crop(priv, crop, cfg, &sdformat->format, sensor); + compose->left = 0; + compose->top = 0; + compose->width = crop->width; + compose->height = crop->height; + + *cc = imx_media_find_mbus_format(sdformat->format.code, + CS_SEL_ANY, true); + if (!*cc) { + imx_media_enum_mbus_format(&code, 0, + CS_SEL_ANY, false); + *cc = imx_media_find_mbus_format(code, + CS_SEL_ANY, false); + sdformat->format.code = (*cc)->codes[0]; + } + + imx_media_fill_default_mbus_fields( + &sdformat->format, infmt, + priv->active_output_pad == CSI_SRC_PAD_DIRECT); + break; + } +} + +static int csi_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *sdformat) +{ + struct csi_priv *priv = v4l2_get_subdevdata(sd); + struct imx_media_video_dev *vdev = priv->vdev; + const struct imx_media_pixfmt *cc; + struct imx_media_subdev *sensor; + struct v4l2_pix_format vdev_fmt; + struct v4l2_mbus_framefmt *fmt; + struct v4l2_rect *crop, *compose; + int ret = 0; + + if (sdformat->pad >= CSI_NUM_PADS) + return -EINVAL; + + sensor = imx_media_find_sensor(priv->md, &priv->sd.entity); + if (IS_ERR(sensor)) { + v4l2_err(&priv->sd, "no sensor attached\n"); + return PTR_ERR(sensor); + } + + mutex_lock(&priv->lock); + + if (priv->stream_count > 0) { + ret = -EBUSY; + goto out; + } + + crop = __csi_get_crop(priv, cfg, sdformat->which); + compose = __csi_get_compose(priv, cfg, sdformat->which); + + csi_try_fmt(priv, sensor, cfg, sdformat, crop, compose, &cc); + + fmt = __csi_get_fmt(priv, cfg, sdformat->pad, sdformat->which); + *fmt = sdformat->format; + + if (sdformat->pad == CSI_SINK_PAD) { + int pad; + + /* propagate format to source pads */ + for (pad = CSI_SINK_PAD + 1; pad < CSI_NUM_PADS; pad++) { + const struct imx_media_pixfmt *outcc; + struct v4l2_mbus_framefmt *outfmt; + struct v4l2_subdev_format format; + + format.pad = pad; + format.which = sdformat->which; + format.format = sdformat->format; + csi_try_fmt(priv, sensor, cfg, &format, NULL, compose, + &outcc); + + outfmt = __csi_get_fmt(priv, cfg, pad, sdformat->which); + *outfmt = format.format; + + if (sdformat->which == V4L2_SUBDEV_FORMAT_ACTIVE) + priv->cc[pad] = outcc; + } + } + + if (sdformat->which == V4L2_SUBDEV_FORMAT_TRY) + goto out; + + priv->cc[sdformat->pad] = cc; + + /* propagate IDMAC output pad format to capture device */ + imx_media_mbus_fmt_to_pix_fmt(&vdev_fmt, + &priv->format_mbus[CSI_SRC_PAD_IDMAC], + priv->cc[CSI_SRC_PAD_IDMAC]); + mutex_unlock(&priv->lock); + imx_media_capture_device_set_format(vdev, &vdev_fmt); + + return 0; +out: + mutex_unlock(&priv->lock); + return ret; +} + +static int csi_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct csi_priv *priv = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *infmt; + struct v4l2_rect *crop, *compose; + int ret = 0; + + if (sel->pad != CSI_SINK_PAD) + return -EINVAL; + + mutex_lock(&priv->lock); + + infmt = __csi_get_fmt(priv, cfg, CSI_SINK_PAD, sel->which); + crop = __csi_get_crop(priv, cfg, sel->which); + compose = __csi_get_compose(priv, cfg, sel->which); + + switch (sel->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = infmt->width; + sel->r.height = infmt->height; + break; + case V4L2_SEL_TGT_CROP: + sel->r = *crop; + break; + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = crop->width; + sel->r.height = crop->height; + break; + case V4L2_SEL_TGT_COMPOSE: + sel->r = *compose; + break; + default: + ret = -EINVAL; + } + + mutex_unlock(&priv->lock); + return ret; +} + +static int csi_set_scale(u32 *compose, u32 crop, u32 flags) +{ + if ((flags & (V4L2_SEL_FLAG_LE | V4L2_SEL_FLAG_GE)) == + (V4L2_SEL_FLAG_LE | V4L2_SEL_FLAG_GE) && + *compose != crop && *compose != crop / 2) + return -ERANGE; + + if (*compose <= crop / 2 || + (*compose < crop * 3 / 4 && !(flags & V4L2_SEL_FLAG_GE)) || + (*compose < crop && (flags & V4L2_SEL_FLAG_LE))) + *compose = crop / 2; + else + *compose = crop; + + return 0; +} + +static int csi_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct csi_priv *priv = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *infmt; + struct v4l2_rect *crop, *compose; + struct imx_media_subdev *sensor; + int pad, ret = 0; + + if (sel->pad != CSI_SINK_PAD) + return -EINVAL; + + sensor = imx_media_find_sensor(priv->md, &priv->sd.entity); + if (IS_ERR(sensor)) { + v4l2_err(&priv->sd, "no sensor attached\n"); + return PTR_ERR(sensor); + } + + mutex_lock(&priv->lock); + + if (priv->stream_count > 0) { + ret = -EBUSY; + goto out; + } + + infmt = __csi_get_fmt(priv, cfg, CSI_SINK_PAD, sel->which); + crop = __csi_get_crop(priv, cfg, sel->which); + compose = __csi_get_compose(priv, cfg, sel->which); + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + /* + * Modifying the crop rectangle always changes the format on + * the source pads. If the KEEP_CONFIG flag is set, just return + * the current crop rectangle. + */ + if (sel->flags & V4L2_SEL_FLAG_KEEP_CONFIG) { + sel->r = priv->crop; + if (sel->which == V4L2_SUBDEV_FORMAT_TRY) + *crop = sel->r; + goto out; + } + + csi_try_crop(priv, &sel->r, cfg, infmt, sensor); + + *crop = sel->r; + + /* Reset scaling to 1:1 */ + compose->width = crop->width; + compose->height = crop->height; + break; + case V4L2_SEL_TGT_COMPOSE: + /* + * Modifying the compose rectangle always changes the format on + * the source pads. If the KEEP_CONFIG flag is set, just return + * the current compose rectangle. + */ + if (sel->flags & V4L2_SEL_FLAG_KEEP_CONFIG) { + sel->r = priv->compose; + if (sel->which == V4L2_SUBDEV_FORMAT_TRY) + *compose = sel->r; + goto out; + } + + sel->r.left = 0; + sel->r.top = 0; + ret = csi_set_scale(&sel->r.width, crop->width, sel->flags); + if (ret) + goto out; + ret = csi_set_scale(&sel->r.height, crop->height, sel->flags); + if (ret) + goto out; + + *compose = sel->r; + break; + default: + ret = -EINVAL; + goto out; + } + + /* Reset source pads to sink compose rectangle */ + for (pad = CSI_SINK_PAD + 1; pad < CSI_NUM_PADS; pad++) { + struct v4l2_mbus_framefmt *outfmt; + + outfmt = __csi_get_fmt(priv, cfg, pad, sel->which); + outfmt->width = compose->width; + outfmt->height = compose->height; + } + +out: + mutex_unlock(&priv->lock); + return ret; +} + +static int csi_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + if (sub->type != V4L2_EVENT_IMX_FRAME_INTERVAL_ERROR) + return -EINVAL; + if (sub->id != 0) + return -EINVAL; + + return v4l2_event_subscribe(fh, sub, 0, NULL); +} + +static int csi_unsubscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + return v4l2_event_unsubscribe(fh, sub); +} + +/* + * retrieve our pads parsed from the OF graph by the media device + */ +static int csi_registered(struct v4l2_subdev *sd) +{ + struct csi_priv *priv = v4l2_get_subdevdata(sd); + int i, ret; + u32 code; + + /* get media device */ + priv->md = dev_get_drvdata(sd->v4l2_dev->dev); + + /* get handle to IPU CSI */ + priv->csi = ipu_csi_get(priv->ipu, priv->csi_id); + if (IS_ERR(priv->csi)) { + v4l2_err(&priv->sd, "failed to get CSI%d\n", priv->csi_id); + return PTR_ERR(priv->csi); + } + + for (i = 0; i < CSI_NUM_PADS; i++) { + priv->pad[i].flags = (i == CSI_SINK_PAD) ? + MEDIA_PAD_FL_SINK : MEDIA_PAD_FL_SOURCE; + + code = 0; + if (i != CSI_SINK_PAD) + imx_media_enum_ipu_format(&code, 0, CS_SEL_YUV); + + /* set a default mbus format */ + ret = imx_media_init_mbus_fmt(&priv->format_mbus[i], + 640, 480, code, V4L2_FIELD_NONE, + &priv->cc[i]); + if (ret) + goto put_csi; + + /* init default frame interval */ + priv->frame_interval[i].numerator = 1; + priv->frame_interval[i].denominator = 30; + } + + /* disable frame skipping */ + priv->skip = &csi_skip[0]; + + /* init default crop and compose rectangle sizes */ + priv->crop.width = 640; + priv->crop.height = 480; + priv->compose.width = 640; + priv->compose.height = 480; + + priv->fim = imx_media_fim_init(&priv->sd); + if (IS_ERR(priv->fim)) { + ret = PTR_ERR(priv->fim); + goto put_csi; + } + + ret = media_entity_pads_init(&sd->entity, CSI_NUM_PADS, priv->pad); + if (ret) + goto free_fim; + + ret = imx_media_capture_device_register(priv->vdev); + if (ret) + goto free_fim; + + ret = imx_media_add_video_device(priv->md, priv->vdev); + if (ret) + goto unreg; + + return 0; +unreg: + imx_media_capture_device_unregister(priv->vdev); +free_fim: + if (priv->fim) + imx_media_fim_free(priv->fim); +put_csi: + ipu_csi_put(priv->csi); + return ret; +} + +static void csi_unregistered(struct v4l2_subdev *sd) +{ + struct csi_priv *priv = v4l2_get_subdevdata(sd); + + imx_media_capture_device_unregister(priv->vdev); + + if (priv->fim) + imx_media_fim_free(priv->fim); + + if (!IS_ERR_OR_NULL(priv->csi)) + ipu_csi_put(priv->csi); +} + +static const struct media_entity_operations csi_entity_ops = { + .link_setup = csi_link_setup, + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct v4l2_subdev_core_ops csi_core_ops = { + .subscribe_event = csi_subscribe_event, + .unsubscribe_event = csi_unsubscribe_event, +}; + +static const struct v4l2_subdev_video_ops csi_video_ops = { + .g_frame_interval = csi_g_frame_interval, + .s_frame_interval = csi_s_frame_interval, + .s_stream = csi_s_stream, +}; + +static const struct v4l2_subdev_pad_ops csi_pad_ops = { + .enum_mbus_code = csi_enum_mbus_code, + .enum_frame_size = csi_enum_frame_size, + .enum_frame_interval = csi_enum_frame_interval, + .get_fmt = csi_get_fmt, + .set_fmt = csi_set_fmt, + .get_selection = csi_get_selection, + .set_selection = csi_set_selection, + .link_validate = csi_link_validate, +}; + +static const struct v4l2_subdev_ops csi_subdev_ops = { + .core = &csi_core_ops, + .video = &csi_video_ops, + .pad = &csi_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops csi_internal_ops = { + .registered = csi_registered, + .unregistered = csi_unregistered, +}; + +static int imx_csi_probe(struct platform_device *pdev) +{ + struct ipu_client_platformdata *pdata; + struct pinctrl *pinctrl; + struct csi_priv *priv; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + platform_set_drvdata(pdev, &priv->sd); + priv->dev = &pdev->dev; + + ret = dma_set_coherent_mask(priv->dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + /* get parent IPU */ + priv->ipu = dev_get_drvdata(priv->dev->parent); + + /* get our CSI id */ + pdata = priv->dev->platform_data; + priv->csi_id = pdata->csi; + priv->smfc_id = (priv->csi_id == 0) ? 0 : 2; + + init_timer(&priv->eof_timeout_timer); + priv->eof_timeout_timer.data = (unsigned long)priv; + priv->eof_timeout_timer.function = csi_idmac_eof_timeout; + spin_lock_init(&priv->irqlock); + + v4l2_subdev_init(&priv->sd, &csi_subdev_ops); + v4l2_set_subdevdata(&priv->sd, priv); + priv->sd.internal_ops = &csi_internal_ops; + priv->sd.entity.ops = &csi_entity_ops; + priv->sd.entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; + priv->sd.dev = &pdev->dev; + priv->sd.fwnode = of_fwnode_handle(pdata->of_node); + priv->sd.owner = THIS_MODULE; + priv->sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; + priv->sd.grp_id = priv->csi_id ? + IMX_MEDIA_GRP_ID_CSI1 : IMX_MEDIA_GRP_ID_CSI0; + imx_media_grp_id_to_sd_name(priv->sd.name, sizeof(priv->sd.name), + priv->sd.grp_id, ipu_get_num(priv->ipu)); + + priv->vdev = imx_media_capture_device_init(&priv->sd, + CSI_SRC_PAD_IDMAC); + if (IS_ERR(priv->vdev)) + return PTR_ERR(priv->vdev); + + mutex_init(&priv->lock); + + v4l2_ctrl_handler_init(&priv->ctrl_hdlr, 0); + priv->sd.ctrl_handler = &priv->ctrl_hdlr; + + /* + * The IPUv3 driver did not assign an of_node to this + * device. As a result, pinctrl does not automatically + * configure our pin groups, so we need to do that manually + * here, after setting this device's of_node. + */ + priv->dev->of_node = pdata->of_node; + pinctrl = devm_pinctrl_get_select_default(priv->dev); + + ret = v4l2_async_register_subdev(&priv->sd); + if (ret) + goto free; + + return 0; +free: + v4l2_ctrl_handler_free(&priv->ctrl_hdlr); + mutex_destroy(&priv->lock); + imx_media_capture_device_remove(priv->vdev); + return ret; +} + +static int imx_csi_remove(struct platform_device *pdev) +{ + struct v4l2_subdev *sd = platform_get_drvdata(pdev); + struct csi_priv *priv = sd_to_dev(sd); + + v4l2_ctrl_handler_free(&priv->ctrl_hdlr); + mutex_destroy(&priv->lock); + imx_media_capture_device_remove(priv->vdev); + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + + return 0; +} + +static const struct platform_device_id imx_csi_ids[] = { + { .name = "imx-ipuv3-csi" }, + { }, +}; +MODULE_DEVICE_TABLE(platform, imx_csi_ids); + +static struct platform_driver imx_csi_driver = { + .probe = imx_csi_probe, + .remove = imx_csi_remove, + .id_table = imx_csi_ids, + .driver = { + .name = "imx-ipuv3-csi", + }, +}; +module_platform_driver(imx_csi_driver); + +MODULE_DESCRIPTION("i.MX CSI subdev driver"); +MODULE_AUTHOR("Steve Longerbeam <steve_longerbeam@mentor.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:imx-ipuv3-csi"); diff --git a/drivers/staging/media/imx/imx-media-dev.c b/drivers/staging/media/imx/imx-media-dev.c new file mode 100644 index 000000000000..48cbc7716758 --- /dev/null +++ b/drivers/staging/media/imx/imx-media-dev.c @@ -0,0 +1,667 @@ +/* + * V4L2 Media Controller Driver for Freescale i.MX5/6 SOC + * + * Copyright (c) 2016 Mentor Graphics Inc. + * + * 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; either version 2 of the License, or + * (at your option) any later version. + */ +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/pinctrl/consumer.h> +#include <linux/platform_device.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/timer.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-event.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-mc.h> +#include <video/imx-ipu-v3.h> +#include <media/imx.h> +#include "imx-media.h" + +static inline struct imx_media_dev *notifier2dev(struct v4l2_async_notifier *n) +{ + return container_of(n, struct imx_media_dev, subdev_notifier); +} + +/* + * Find a subdev by device node or device name. This is called during + * driver load to form the async subdev list and bind them. + */ +struct imx_media_subdev * +imx_media_find_async_subdev(struct imx_media_dev *imxmd, + struct device_node *np, + const char *devname) +{ + struct fwnode_handle *fwnode = np ? of_fwnode_handle(np) : NULL; + struct imx_media_subdev *imxsd; + int i; + + for (i = 0; i < imxmd->subdev_notifier.num_subdevs; i++) { + imxsd = &imxmd->subdev[i]; + switch (imxsd->asd.match_type) { + case V4L2_ASYNC_MATCH_FWNODE: + if (fwnode && imxsd->asd.match.fwnode.fwnode == fwnode) + return imxsd; + break; + case V4L2_ASYNC_MATCH_DEVNAME: + if (devname && + !strcmp(imxsd->asd.match.device_name.name, devname)) + return imxsd; + break; + default: + break; + } + } + + return NULL; +} + + +/* + * Adds a subdev to the async subdev list. If np is non-NULL, adds + * the async as a V4L2_ASYNC_MATCH_FWNODE match type, otherwise as + * a V4L2_ASYNC_MATCH_DEVNAME match type using the dev_name of the + * given platform_device. This is called during driver load when + * forming the async subdev list. + */ +struct imx_media_subdev * +imx_media_add_async_subdev(struct imx_media_dev *imxmd, + struct device_node *np, + struct platform_device *pdev) +{ + struct imx_media_subdev *imxsd; + struct v4l2_async_subdev *asd; + const char *devname = NULL; + int sd_idx; + + mutex_lock(&imxmd->mutex); + + if (pdev) + devname = dev_name(&pdev->dev); + + /* return NULL if this subdev already added */ + if (imx_media_find_async_subdev(imxmd, np, devname)) { + dev_dbg(imxmd->md.dev, "%s: already added %s\n", + __func__, np ? np->name : devname); + imxsd = NULL; + goto out; + } + + sd_idx = imxmd->subdev_notifier.num_subdevs; + if (sd_idx >= IMX_MEDIA_MAX_SUBDEVS) { + dev_err(imxmd->md.dev, "%s: too many subdevs! can't add %s\n", + __func__, np ? np->name : devname); + imxsd = ERR_PTR(-ENOSPC); + goto out; + } + + imxsd = &imxmd->subdev[sd_idx]; + + asd = &imxsd->asd; + if (np) { + asd->match_type = V4L2_ASYNC_MATCH_FWNODE; + asd->match.fwnode.fwnode = of_fwnode_handle(np); + } else { + asd->match_type = V4L2_ASYNC_MATCH_DEVNAME; + strncpy(imxsd->devname, devname, sizeof(imxsd->devname)); + asd->match.device_name.name = imxsd->devname; + imxsd->pdev = pdev; + } + + imxmd->async_ptrs[sd_idx] = asd; + imxmd->subdev_notifier.num_subdevs++; + + dev_dbg(imxmd->md.dev, "%s: added %s, match type %s\n", + __func__, np ? np->name : devname, np ? "FWNODE" : "DEVNAME"); + +out: + mutex_unlock(&imxmd->mutex); + return imxsd; +} + +/* + * Adds an imx-media link to a subdev pad's link list. This is called + * during driver load when forming the links between subdevs. + * + * @pad: the local pad + * @remote_node: the device node of the remote subdev + * @remote_devname: the device name of the remote subdev + * @local_pad: local pad index + * @remote_pad: remote pad index + */ +int imx_media_add_pad_link(struct imx_media_dev *imxmd, + struct imx_media_pad *pad, + struct device_node *remote_node, + const char *remote_devname, + int local_pad, int remote_pad) +{ + struct imx_media_link *link; + int link_idx, ret = 0; + + mutex_lock(&imxmd->mutex); + + link_idx = pad->num_links; + if (link_idx >= IMX_MEDIA_MAX_LINKS) { + dev_err(imxmd->md.dev, "%s: too many links!\n", __func__); + ret = -ENOSPC; + goto out; + } + + link = &pad->link[link_idx]; + + link->remote_sd_node = remote_node; + if (remote_devname) + strncpy(link->remote_devname, remote_devname, + sizeof(link->remote_devname)); + + link->local_pad = local_pad; + link->remote_pad = remote_pad; + + pad->num_links++; +out: + mutex_unlock(&imxmd->mutex); + return ret; +} + +/* + * get IPU from this CSI and add it to the list of IPUs + * the media driver will control. + */ +static int imx_media_get_ipu(struct imx_media_dev *imxmd, + struct v4l2_subdev *csi_sd) +{ + struct ipu_soc *ipu; + int ipu_id; + + ipu = dev_get_drvdata(csi_sd->dev->parent); + if (!ipu) { + v4l2_err(&imxmd->v4l2_dev, + "CSI %s has no parent IPU!\n", csi_sd->name); + return -ENODEV; + } + + ipu_id = ipu_get_num(ipu); + if (ipu_id > 1) { + v4l2_err(&imxmd->v4l2_dev, "invalid IPU id %d!\n", ipu_id); + return -ENODEV; + } + + if (!imxmd->ipu[ipu_id]) + imxmd->ipu[ipu_id] = ipu; + + return 0; +} + +/* async subdev bound notifier */ +static int imx_media_subdev_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_subdev *asd) +{ + struct imx_media_dev *imxmd = notifier2dev(notifier); + struct device_node *np = to_of_node(sd->fwnode); + struct imx_media_subdev *imxsd; + int ret = 0; + + mutex_lock(&imxmd->mutex); + + imxsd = imx_media_find_async_subdev(imxmd, np, dev_name(sd->dev)); + if (!imxsd) { + ret = -EINVAL; + goto out; + } + + if (sd->grp_id & IMX_MEDIA_GRP_ID_CSI) { + ret = imx_media_get_ipu(imxmd, sd); + if (ret) + goto out_unlock; + } else if (sd->entity.function == MEDIA_ENT_F_VID_MUX) { + /* this is a video mux */ + sd->grp_id = IMX_MEDIA_GRP_ID_VIDMUX; + } else if (imxsd->num_sink_pads == 0) { + /* + * this is an original source of video frames, it + * could be a camera sensor, an analog decoder, or + * a bridge device (HDMI -> MIPI CSI-2 for example). + * This group ID is used to locate the entity that + * is the original source of video in a pipeline. + */ + sd->grp_id = IMX_MEDIA_GRP_ID_SENSOR; + } + + /* attach the subdev */ + imxsd->sd = sd; +out: + if (ret) + v4l2_warn(&imxmd->v4l2_dev, + "Received unknown subdev %s\n", sd->name); + else + v4l2_info(&imxmd->v4l2_dev, + "Registered subdev %s\n", sd->name); + +out_unlock: + mutex_unlock(&imxmd->mutex); + return ret; +} + +/* + * Create a single source->sink media link given a subdev and a single + * link from one of its source pads. Called after all subdevs have + * registered. + */ +static int imx_media_create_link(struct imx_media_dev *imxmd, + struct imx_media_subdev *src, + struct imx_media_link *link) +{ + struct imx_media_subdev *sink; + u16 source_pad, sink_pad; + int ret; + + sink = imx_media_find_async_subdev(imxmd, link->remote_sd_node, + link->remote_devname); + if (!sink) { + v4l2_warn(&imxmd->v4l2_dev, "%s: no sink for %s:%d\n", + __func__, src->sd->name, link->local_pad); + return 0; + } + + source_pad = link->local_pad; + sink_pad = link->remote_pad; + + v4l2_info(&imxmd->v4l2_dev, "%s: %s:%d -> %s:%d\n", __func__, + src->sd->name, source_pad, sink->sd->name, sink_pad); + + ret = media_create_pad_link(&src->sd->entity, source_pad, + &sink->sd->entity, sink_pad, 0); + if (ret) + v4l2_err(&imxmd->v4l2_dev, + "create_pad_link failed: %d\n", ret); + + return ret; +} + +/* + * create the media links from all imx-media pads and their links. + * Called after all subdevs have registered. + */ +static int imx_media_create_links(struct imx_media_dev *imxmd) +{ + struct imx_media_subdev *imxsd; + struct imx_media_link *link; + struct imx_media_pad *pad; + int num_pads, i, j, k; + int ret = 0; + + for (i = 0; i < imxmd->num_subdevs; i++) { + imxsd = &imxmd->subdev[i]; + num_pads = imxsd->num_sink_pads + imxsd->num_src_pads; + + for (j = 0; j < num_pads; j++) { + pad = &imxsd->pad[j]; + + /* only create the source->sink links */ + if (!(pad->pad.flags & MEDIA_PAD_FL_SOURCE)) + continue; + + for (k = 0; k < pad->num_links; k++) { + link = &pad->link[k]; + + ret = imx_media_create_link(imxmd, imxsd, link); + if (ret) + goto out; + } + } + } + +out: + return ret; +} + +/* + * adds given video device to given imx-media source pad vdev list. + * Continues upstream from the pad entity's sink pads. + */ +static int imx_media_add_vdev_to_pad(struct imx_media_dev *imxmd, + struct imx_media_video_dev *vdev, + struct media_pad *srcpad) +{ + struct media_entity *entity = srcpad->entity; + struct imx_media_subdev *imxsd; + struct imx_media_pad *imxpad; + struct media_link *link; + struct v4l2_subdev *sd; + int i, vdev_idx, ret; + + /* skip this entity if not a v4l2_subdev */ + if (!is_media_entity_v4l2_subdev(entity)) + return 0; + + sd = media_entity_to_v4l2_subdev(entity); + imxsd = imx_media_find_subdev_by_sd(imxmd, sd); + if (IS_ERR(imxsd)) + return PTR_ERR(imxsd); + + imxpad = &imxsd->pad[srcpad->index]; + vdev_idx = imxpad->num_vdevs; + + /* just return if we've been here before */ + for (i = 0; i < vdev_idx; i++) + if (vdev == imxpad->vdev[i]) + return 0; + + if (vdev_idx >= IMX_MEDIA_MAX_VDEVS) { + dev_err(imxmd->md.dev, "can't add %s to pad %s:%u\n", + vdev->vfd->entity.name, entity->name, srcpad->index); + return -ENOSPC; + } + + dev_dbg(imxmd->md.dev, "adding %s to pad %s:%u\n", + vdev->vfd->entity.name, entity->name, srcpad->index); + imxpad->vdev[vdev_idx] = vdev; + imxpad->num_vdevs++; + + /* move upstream from this entity's sink pads */ + for (i = 0; i < entity->num_pads; i++) { + struct media_pad *pad = &entity->pads[i]; + + if (!(pad->flags & MEDIA_PAD_FL_SINK)) + continue; + + list_for_each_entry(link, &entity->links, list) { + if (link->sink != pad) + continue; + ret = imx_media_add_vdev_to_pad(imxmd, vdev, + link->source); + if (ret) + return ret; + } + } + + return 0; +} + +/* form the vdev lists in all imx-media source pads */ +static int imx_media_create_pad_vdev_lists(struct imx_media_dev *imxmd) +{ + struct imx_media_video_dev *vdev; + struct media_link *link; + int i, ret; + + for (i = 0; i < imxmd->num_vdevs; i++) { + vdev = imxmd->vdev[i]; + link = list_first_entry(&vdev->vfd->entity.links, + struct media_link, list); + ret = imx_media_add_vdev_to_pad(imxmd, vdev, link->source); + if (ret) + break; + } + + return ret; +} + +/* async subdev complete notifier */ +static int imx_media_probe_complete(struct v4l2_async_notifier *notifier) +{ + struct imx_media_dev *imxmd = notifier2dev(notifier); + int i, ret; + + mutex_lock(&imxmd->mutex); + + /* make sure all subdevs were bound */ + for (i = 0; i < imxmd->num_subdevs; i++) { + if (!imxmd->subdev[i].sd) { + v4l2_err(&imxmd->v4l2_dev, "unbound subdev!\n"); + ret = -ENODEV; + goto unlock; + } + } + + ret = imx_media_create_links(imxmd); + if (ret) + goto unlock; + + ret = imx_media_create_pad_vdev_lists(imxmd); + if (ret) + goto unlock; + + ret = v4l2_device_register_subdev_nodes(&imxmd->v4l2_dev); +unlock: + mutex_unlock(&imxmd->mutex); + if (ret) + return ret; + + return media_device_register(&imxmd->md); +} + +/* + * adds controls to a video device from an entity subdevice. + * Continues upstream from the entity's sink pads. + */ +static int imx_media_inherit_controls(struct imx_media_dev *imxmd, + struct video_device *vfd, + struct media_entity *entity) +{ + int i, ret = 0; + + if (is_media_entity_v4l2_subdev(entity)) { + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); + + dev_dbg(imxmd->md.dev, + "adding controls to %s from %s\n", + vfd->entity.name, sd->entity.name); + + ret = v4l2_ctrl_add_handler(vfd->ctrl_handler, + sd->ctrl_handler, + NULL); + if (ret) + return ret; + } + + /* move upstream */ + for (i = 0; i < entity->num_pads; i++) { + struct media_pad *pad, *spad = &entity->pads[i]; + + if (!(spad->flags & MEDIA_PAD_FL_SINK)) + continue; + + pad = media_entity_remote_pad(spad); + if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) + continue; + + ret = imx_media_inherit_controls(imxmd, vfd, pad->entity); + if (ret) + break; + } + + return ret; +} + +static int imx_media_link_notify(struct media_link *link, u32 flags, + unsigned int notification) +{ + struct media_entity *source = link->source->entity; + struct imx_media_subdev *imxsd; + struct imx_media_pad *imxpad; + struct imx_media_dev *imxmd; + struct video_device *vfd; + struct v4l2_subdev *sd; + int i, pad_idx, ret; + + ret = v4l2_pipeline_link_notify(link, flags, notification); + if (ret) + return ret; + + /* don't bother if source is not a subdev */ + if (!is_media_entity_v4l2_subdev(source)) + return 0; + + sd = media_entity_to_v4l2_subdev(source); + pad_idx = link->source->index; + + imxmd = dev_get_drvdata(sd->v4l2_dev->dev); + + imxsd = imx_media_find_subdev_by_sd(imxmd, sd); + if (IS_ERR(imxsd)) + return PTR_ERR(imxsd); + imxpad = &imxsd->pad[pad_idx]; + + /* + * Before disabling a link, reset controls for all video + * devices reachable from this link. + * + * After enabling a link, refresh controls for all video + * devices reachable from this link. + */ + if (notification == MEDIA_DEV_NOTIFY_PRE_LINK_CH && + !(flags & MEDIA_LNK_FL_ENABLED)) { + for (i = 0; i < imxpad->num_vdevs; i++) { + vfd = imxpad->vdev[i]->vfd; + dev_dbg(imxmd->md.dev, + "reset controls for %s\n", + vfd->entity.name); + v4l2_ctrl_handler_free(vfd->ctrl_handler); + v4l2_ctrl_handler_init(vfd->ctrl_handler, 0); + } + } else if (notification == MEDIA_DEV_NOTIFY_POST_LINK_CH && + (link->flags & MEDIA_LNK_FL_ENABLED)) { + for (i = 0; i < imxpad->num_vdevs; i++) { + vfd = imxpad->vdev[i]->vfd; + dev_dbg(imxmd->md.dev, + "refresh controls for %s\n", + vfd->entity.name); + ret = imx_media_inherit_controls(imxmd, vfd, + &vfd->entity); + if (ret) + break; + } + } + + return ret; +} + +static const struct media_device_ops imx_media_md_ops = { + .link_notify = imx_media_link_notify, +}; + +static int imx_media_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct imx_media_subdev *csi[4] = {0}; + struct imx_media_dev *imxmd; + int ret; + + imxmd = devm_kzalloc(dev, sizeof(*imxmd), GFP_KERNEL); + if (!imxmd) + return -ENOMEM; + + dev_set_drvdata(dev, imxmd); + + strlcpy(imxmd->md.model, "imx-media", sizeof(imxmd->md.model)); + imxmd->md.ops = &imx_media_md_ops; + imxmd->md.dev = dev; + + mutex_init(&imxmd->mutex); + + imxmd->v4l2_dev.mdev = &imxmd->md; + strlcpy(imxmd->v4l2_dev.name, "imx-media", + sizeof(imxmd->v4l2_dev.name)); + + media_device_init(&imxmd->md); + + ret = v4l2_device_register(dev, &imxmd->v4l2_dev); + if (ret < 0) { + v4l2_err(&imxmd->v4l2_dev, + "Failed to register v4l2_device: %d\n", ret); + goto cleanup; + } + + dev_set_drvdata(imxmd->v4l2_dev.dev, imxmd); + + ret = imx_media_of_parse(imxmd, &csi, node); + if (ret) { + v4l2_err(&imxmd->v4l2_dev, + "imx_media_of_parse failed with %d\n", ret); + goto unreg_dev; + } + + ret = imx_media_add_internal_subdevs(imxmd, csi); + if (ret) { + v4l2_err(&imxmd->v4l2_dev, + "add_internal_subdevs failed with %d\n", ret); + goto unreg_dev; + } + + /* no subdevs? just bail */ + imxmd->num_subdevs = imxmd->subdev_notifier.num_subdevs; + if (imxmd->num_subdevs == 0) { + ret = -ENODEV; + goto unreg_dev; + } + + /* prepare the async subdev notifier and register it */ + imxmd->subdev_notifier.subdevs = imxmd->async_ptrs; + imxmd->subdev_notifier.bound = imx_media_subdev_bound; + imxmd->subdev_notifier.complete = imx_media_probe_complete; + ret = v4l2_async_notifier_register(&imxmd->v4l2_dev, + &imxmd->subdev_notifier); + if (ret) { + v4l2_err(&imxmd->v4l2_dev, + "v4l2_async_notifier_register failed with %d\n", ret); + goto del_int; + } + + return 0; + +del_int: + imx_media_remove_internal_subdevs(imxmd); +unreg_dev: + v4l2_device_unregister(&imxmd->v4l2_dev); +cleanup: + media_device_cleanup(&imxmd->md); + return ret; +} + +static int imx_media_remove(struct platform_device *pdev) +{ + struct imx_media_dev *imxmd = + (struct imx_media_dev *)platform_get_drvdata(pdev); + + v4l2_info(&imxmd->v4l2_dev, "Removing imx-media\n"); + + v4l2_async_notifier_unregister(&imxmd->subdev_notifier); + imx_media_remove_internal_subdevs(imxmd); + v4l2_device_unregister(&imxmd->v4l2_dev); + media_device_unregister(&imxmd->md); + media_device_cleanup(&imxmd->md); + + return 0; +} + +static const struct of_device_id imx_media_dt_ids[] = { + { .compatible = "fsl,imx-capture-subsystem" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_media_dt_ids); + +static struct platform_driver imx_media_pdrv = { + .probe = imx_media_probe, + .remove = imx_media_remove, + .driver = { + .name = "imx-media", + .of_match_table = imx_media_dt_ids, + }, +}; + +module_platform_driver(imx_media_pdrv); + +MODULE_DESCRIPTION("i.MX5/6 v4l2 media controller driver"); +MODULE_AUTHOR("Steve Longerbeam <steve_longerbeam@mentor.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/media/imx/imx-media-fim.c b/drivers/staging/media/imx/imx-media-fim.c new file mode 100644 index 000000000000..47275ef803f3 --- /dev/null +++ b/drivers/staging/media/imx/imx-media-fim.c @@ -0,0 +1,494 @@ +/* + * Frame Interval Monitor. + * + * Copyright (c) 2016 Mentor Graphics Inc. + * + * 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; either version 2 of the License, or + * (at your option) any later version. + */ +#include <linux/delay.h> +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-subdev.h> +#include <media/imx.h> +#include "imx-media.h" + +enum { + FIM_CL_ENABLE = 0, + FIM_CL_NUM, + FIM_CL_TOLERANCE_MIN, + FIM_CL_TOLERANCE_MAX, + FIM_CL_NUM_SKIP, + FIM_NUM_CONTROLS, +}; + +enum { + FIM_CL_ICAP_EDGE = 0, + FIM_CL_ICAP_CHANNEL, + FIM_NUM_ICAP_CONTROLS, +}; + +#define FIM_CL_ENABLE_DEF 0 /* FIM disabled by default */ +#define FIM_CL_NUM_DEF 8 /* average 8 frames */ +#define FIM_CL_NUM_SKIP_DEF 2 /* skip 2 frames after restart */ +#define FIM_CL_TOLERANCE_MIN_DEF 50 /* usec */ +#define FIM_CL_TOLERANCE_MAX_DEF 0 /* no max tolerance (unbounded) */ + +struct imx_media_fim { + struct imx_media_dev *md; + + /* the owning subdev of this fim instance */ + struct v4l2_subdev *sd; + + /* FIM's control handler */ + struct v4l2_ctrl_handler ctrl_handler; + + /* control clusters */ + struct v4l2_ctrl *ctrl[FIM_NUM_CONTROLS]; + struct v4l2_ctrl *icap_ctrl[FIM_NUM_ICAP_CONTROLS]; + + spinlock_t lock; /* protect control values */ + + /* current control values */ + bool enabled; + int num_avg; + int num_skip; + unsigned long tolerance_min; /* usec */ + unsigned long tolerance_max; /* usec */ + /* input capture method of measuring FI */ + int icap_channel; + int icap_flags; + + int counter; + struct timespec last_ts; + unsigned long sum; /* usec */ + unsigned long nominal; /* usec */ + + struct completion icap_first_event; + bool stream_on; +}; + +#define icap_enabled(fim) ((fim)->icap_flags != IRQ_TYPE_NONE) + +static void update_fim_nominal(struct imx_media_fim *fim, + const struct v4l2_fract *fi) +{ + if (fi->denominator == 0) { + dev_dbg(fim->sd->dev, "no frame interval, FIM disabled\n"); + fim->enabled = false; + return; + } + + fim->nominal = DIV_ROUND_CLOSEST_ULL(1000000ULL * (u64)fi->numerator, + fi->denominator); + + dev_dbg(fim->sd->dev, "FI=%lu usec\n", fim->nominal); +} + +static void reset_fim(struct imx_media_fim *fim, bool curval) +{ + struct v4l2_ctrl *icap_chan = fim->icap_ctrl[FIM_CL_ICAP_CHANNEL]; + struct v4l2_ctrl *icap_edge = fim->icap_ctrl[FIM_CL_ICAP_EDGE]; + struct v4l2_ctrl *en = fim->ctrl[FIM_CL_ENABLE]; + struct v4l2_ctrl *num = fim->ctrl[FIM_CL_NUM]; + struct v4l2_ctrl *skip = fim->ctrl[FIM_CL_NUM_SKIP]; + struct v4l2_ctrl *tol_min = fim->ctrl[FIM_CL_TOLERANCE_MIN]; + struct v4l2_ctrl *tol_max = fim->ctrl[FIM_CL_TOLERANCE_MAX]; + + if (curval) { + fim->enabled = en->cur.val; + fim->icap_flags = icap_edge->cur.val; + fim->icap_channel = icap_chan->cur.val; + fim->num_avg = num->cur.val; + fim->num_skip = skip->cur.val; + fim->tolerance_min = tol_min->cur.val; + fim->tolerance_max = tol_max->cur.val; + } else { + fim->enabled = en->val; + fim->icap_flags = icap_edge->val; + fim->icap_channel = icap_chan->val; + fim->num_avg = num->val; + fim->num_skip = skip->val; + fim->tolerance_min = tol_min->val; + fim->tolerance_max = tol_max->val; + } + + /* disable tolerance range if max <= min */ + if (fim->tolerance_max <= fim->tolerance_min) + fim->tolerance_max = 0; + + /* num_skip must be >= 1 if input capture not used */ + if (!icap_enabled(fim)) + fim->num_skip = max_t(int, fim->num_skip, 1); + + fim->counter = -fim->num_skip; + fim->sum = 0; +} + +static void send_fim_event(struct imx_media_fim *fim, unsigned long error) +{ + static const struct v4l2_event ev = { + .type = V4L2_EVENT_IMX_FRAME_INTERVAL_ERROR, + }; + + v4l2_subdev_notify_event(fim->sd, &ev); +} + +/* + * Monitor an averaged frame interval. If the average deviates too much + * from the nominal frame rate, send the frame interval error event. The + * frame intervals are averaged in order to quiet noise from + * (presumably random) interrupt latency. + */ +static void frame_interval_monitor(struct imx_media_fim *fim, + struct timespec *ts) +{ + unsigned long interval, error, error_avg; + bool send_event = false; + struct timespec diff; + + if (!fim->enabled || ++fim->counter <= 0) + goto out_update_ts; + + diff = timespec_sub(*ts, fim->last_ts); + interval = diff.tv_sec * 1000 * 1000 + diff.tv_nsec / 1000; + error = abs(interval - fim->nominal); + + if (fim->tolerance_max && error >= fim->tolerance_max) { + dev_dbg(fim->sd->dev, + "FIM: %lu ignored, out of tolerance bounds\n", + error); + fim->counter--; + goto out_update_ts; + } + + fim->sum += error; + + if (fim->counter == fim->num_avg) { + error_avg = DIV_ROUND_CLOSEST(fim->sum, fim->num_avg); + + if (error_avg > fim->tolerance_min) + send_event = true; + + dev_dbg(fim->sd->dev, "FIM: error: %lu usec%s\n", + error_avg, send_event ? " (!!!)" : ""); + + fim->counter = 0; + fim->sum = 0; + } + +out_update_ts: + fim->last_ts = *ts; + if (send_event) + send_fim_event(fim, error_avg); +} + +#ifdef CONFIG_IMX_GPT_ICAP +/* + * Input Capture method of measuring frame intervals. Not subject + * to interrupt latency. + */ +static void fim_input_capture_handler(int channel, void *dev_id, + struct timespec *ts) +{ + struct imx_media_fim *fim = dev_id; + unsigned long flags; + + spin_lock_irqsave(&fim->lock, flags); + + frame_interval_monitor(fim, ts); + + if (!completion_done(&fim->icap_first_event)) + complete(&fim->icap_first_event); + + spin_unlock_irqrestore(&fim->lock, flags); +} + +static int fim_request_input_capture(struct imx_media_fim *fim) +{ + init_completion(&fim->icap_first_event); + + return mxc_request_input_capture(fim->icap_channel, + fim_input_capture_handler, + fim->icap_flags, fim); +} + +static void fim_free_input_capture(struct imx_media_fim *fim) +{ + mxc_free_input_capture(fim->icap_channel, fim); +} + +#else /* CONFIG_IMX_GPT_ICAP */ + +static int fim_request_input_capture(struct imx_media_fim *fim) +{ + return 0; +} + +static void fim_free_input_capture(struct imx_media_fim *fim) +{ +} + +#endif /* CONFIG_IMX_GPT_ICAP */ + +/* + * In case we are monitoring the first frame interval after streamon + * (when fim->num_skip = 0), we need a valid fim->last_ts before we + * can begin. This only applies to the input capture method. It is not + * possible to accurately measure the first FI after streamon using the + * EOF method, so fim->num_skip minimum is set to 1 in that case, so this + * function is a noop when the EOF method is used. + */ +static void fim_acquire_first_ts(struct imx_media_fim *fim) +{ + unsigned long ret; + + if (!fim->enabled || fim->num_skip > 0) + return; + + ret = wait_for_completion_timeout( + &fim->icap_first_event, + msecs_to_jiffies(IMX_MEDIA_EOF_TIMEOUT)); + if (ret == 0) + v4l2_warn(fim->sd, "wait first icap event timeout\n"); +} + +/* FIM Controls */ +static int fim_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct imx_media_fim *fim = container_of(ctrl->handler, + struct imx_media_fim, + ctrl_handler); + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&fim->lock, flags); + + switch (ctrl->id) { + case V4L2_CID_IMX_FIM_ENABLE: + break; + case V4L2_CID_IMX_FIM_ICAP_EDGE: + if (fim->stream_on) + ret = -EBUSY; + break; + default: + ret = -EINVAL; + } + + if (!ret) + reset_fim(fim, false); + + spin_unlock_irqrestore(&fim->lock, flags); + return ret; +} + +static const struct v4l2_ctrl_ops fim_ctrl_ops = { + .s_ctrl = fim_s_ctrl, +}; + +static const struct v4l2_ctrl_config fim_ctrl[] = { + [FIM_CL_ENABLE] = { + .ops = &fim_ctrl_ops, + .id = V4L2_CID_IMX_FIM_ENABLE, + .name = "FIM Enable", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .def = FIM_CL_ENABLE_DEF, + .min = 0, + .max = 1, + .step = 1, + }, + [FIM_CL_NUM] = { + .ops = &fim_ctrl_ops, + .id = V4L2_CID_IMX_FIM_NUM, + .name = "FIM Num Average", + .type = V4L2_CTRL_TYPE_INTEGER, + .def = FIM_CL_NUM_DEF, + .min = 1, /* no averaging */ + .max = 64, /* average 64 frames */ + .step = 1, + }, + [FIM_CL_TOLERANCE_MIN] = { + .ops = &fim_ctrl_ops, + .id = V4L2_CID_IMX_FIM_TOLERANCE_MIN, + .name = "FIM Tolerance Min", + .type = V4L2_CTRL_TYPE_INTEGER, + .def = FIM_CL_TOLERANCE_MIN_DEF, + .min = 2, + .max = 200, + .step = 1, + }, + [FIM_CL_TOLERANCE_MAX] = { + .ops = &fim_ctrl_ops, + .id = V4L2_CID_IMX_FIM_TOLERANCE_MAX, + .name = "FIM Tolerance Max", + .type = V4L2_CTRL_TYPE_INTEGER, + .def = FIM_CL_TOLERANCE_MAX_DEF, + .min = 0, + .max = 500, + .step = 1, + }, + [FIM_CL_NUM_SKIP] = { + .ops = &fim_ctrl_ops, + .id = V4L2_CID_IMX_FIM_NUM_SKIP, + .name = "FIM Num Skip", + .type = V4L2_CTRL_TYPE_INTEGER, + .def = FIM_CL_NUM_SKIP_DEF, + .min = 0, /* skip no frames */ + .max = 256, /* skip 256 frames */ + .step = 1, + }, +}; + +static const struct v4l2_ctrl_config fim_icap_ctrl[] = { + [FIM_CL_ICAP_EDGE] = { + .ops = &fim_ctrl_ops, + .id = V4L2_CID_IMX_FIM_ICAP_EDGE, + .name = "FIM Input Capture Edge", + .type = V4L2_CTRL_TYPE_INTEGER, + .def = IRQ_TYPE_NONE, /* input capture disabled by default */ + .min = IRQ_TYPE_NONE, + .max = IRQ_TYPE_EDGE_BOTH, + .step = 1, + }, + [FIM_CL_ICAP_CHANNEL] = { + .ops = &fim_ctrl_ops, + .id = V4L2_CID_IMX_FIM_ICAP_CHANNEL, + .name = "FIM Input Capture Channel", + .type = V4L2_CTRL_TYPE_INTEGER, + .def = 0, + .min = 0, + .max = 1, + .step = 1, + }, +}; + +static int init_fim_controls(struct imx_media_fim *fim) +{ + struct v4l2_ctrl_handler *hdlr = &fim->ctrl_handler; + int i, ret; + + v4l2_ctrl_handler_init(hdlr, FIM_NUM_CONTROLS + FIM_NUM_ICAP_CONTROLS); + + for (i = 0; i < FIM_NUM_CONTROLS; i++) + fim->ctrl[i] = v4l2_ctrl_new_custom(hdlr, + &fim_ctrl[i], + NULL); + for (i = 0; i < FIM_NUM_ICAP_CONTROLS; i++) + fim->icap_ctrl[i] = v4l2_ctrl_new_custom(hdlr, + &fim_icap_ctrl[i], + NULL); + if (hdlr->error) { + ret = hdlr->error; + goto err_free; + } + + v4l2_ctrl_cluster(FIM_NUM_CONTROLS, fim->ctrl); + v4l2_ctrl_cluster(FIM_NUM_ICAP_CONTROLS, fim->icap_ctrl); + + return 0; +err_free: + v4l2_ctrl_handler_free(hdlr); + return ret; +} + +/* + * Monitor frame intervals via EOF interrupt. This method is + * subject to uncertainty errors introduced by interrupt latency. + * + * This is a noop if the Input Capture method is being used, since + * the frame_interval_monitor() is called by the input capture event + * callback handler in that case. + */ +void imx_media_fim_eof_monitor(struct imx_media_fim *fim, struct timespec *ts) +{ + unsigned long flags; + + spin_lock_irqsave(&fim->lock, flags); + + if (!icap_enabled(fim)) + frame_interval_monitor(fim, ts); + + spin_unlock_irqrestore(&fim->lock, flags); +} +EXPORT_SYMBOL_GPL(imx_media_fim_eof_monitor); + +/* Called by the subdev in its s_stream callback */ +int imx_media_fim_set_stream(struct imx_media_fim *fim, + const struct v4l2_fract *fi, + bool on) +{ + unsigned long flags; + int ret = 0; + + v4l2_ctrl_lock(fim->ctrl[FIM_CL_ENABLE]); + + if (fim->stream_on == on) + goto out; + + if (on) { + spin_lock_irqsave(&fim->lock, flags); + reset_fim(fim, true); + update_fim_nominal(fim, fi); + spin_unlock_irqrestore(&fim->lock, flags); + + if (icap_enabled(fim)) { + ret = fim_request_input_capture(fim); + if (ret) + goto out; + fim_acquire_first_ts(fim); + } + } else { + if (icap_enabled(fim)) + fim_free_input_capture(fim); + } + + fim->stream_on = on; +out: + v4l2_ctrl_unlock(fim->ctrl[FIM_CL_ENABLE]); + return ret; +} +EXPORT_SYMBOL_GPL(imx_media_fim_set_stream); + +int imx_media_fim_add_controls(struct imx_media_fim *fim) +{ + /* add the FIM controls to the calling subdev ctrl handler */ + return v4l2_ctrl_add_handler(fim->sd->ctrl_handler, + &fim->ctrl_handler, NULL); +} +EXPORT_SYMBOL_GPL(imx_media_fim_add_controls); + +/* Called by the subdev in its subdev registered callback */ +struct imx_media_fim *imx_media_fim_init(struct v4l2_subdev *sd) +{ + struct imx_media_fim *fim; + int ret; + + fim = devm_kzalloc(sd->dev, sizeof(*fim), GFP_KERNEL); + if (!fim) + return ERR_PTR(-ENOMEM); + + /* get media device */ + fim->md = dev_get_drvdata(sd->v4l2_dev->dev); + fim->sd = sd; + + spin_lock_init(&fim->lock); + + ret = init_fim_controls(fim); + if (ret) + return ERR_PTR(ret); + + return fim; +} +EXPORT_SYMBOL_GPL(imx_media_fim_init); + +void imx_media_fim_free(struct imx_media_fim *fim) +{ + v4l2_ctrl_handler_free(&fim->ctrl_handler); +} +EXPORT_SYMBOL_GPL(imx_media_fim_free); diff --git a/drivers/staging/media/imx/imx-media-internal-sd.c b/drivers/staging/media/imx/imx-media-internal-sd.c new file mode 100644 index 000000000000..cdfbf40dfcbe --- /dev/null +++ b/drivers/staging/media/imx/imx-media-internal-sd.c @@ -0,0 +1,349 @@ +/* + * Media driver for Freescale i.MX5/6 SOC + * + * Adds the internal subdevices and the media links between them. + * + * Copyright (c) 2016 Mentor Graphics Inc. + * + * 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; either version 2 of the License, or + * (at your option) any later version. + */ +#include <linux/platform_device.h> +#include "imx-media.h" + +enum isd_enum { + isd_csi0 = 0, + isd_csi1, + isd_vdic, + isd_ic_prp, + isd_ic_prpenc, + isd_ic_prpvf, + num_isd, +}; + +static const struct internal_subdev_id { + enum isd_enum index; + const char *name; + u32 grp_id; +} isd_id[num_isd] = { + [isd_csi0] = { + .index = isd_csi0, + .grp_id = IMX_MEDIA_GRP_ID_CSI0, + .name = "imx-ipuv3-csi", + }, + [isd_csi1] = { + .index = isd_csi1, + .grp_id = IMX_MEDIA_GRP_ID_CSI1, + .name = "imx-ipuv3-csi", + }, + [isd_vdic] = { + .index = isd_vdic, + .grp_id = IMX_MEDIA_GRP_ID_VDIC, + .name = "imx-ipuv3-vdic", + }, + [isd_ic_prp] = { + .index = isd_ic_prp, + .grp_id = IMX_MEDIA_GRP_ID_IC_PRP, + .name = "imx-ipuv3-ic", + }, + [isd_ic_prpenc] = { + .index = isd_ic_prpenc, + .grp_id = IMX_MEDIA_GRP_ID_IC_PRPENC, + .name = "imx-ipuv3-ic", + }, + [isd_ic_prpvf] = { + .index = isd_ic_prpvf, + .grp_id = IMX_MEDIA_GRP_ID_IC_PRPVF, + .name = "imx-ipuv3-ic", + }, +}; + +struct internal_link { + const struct internal_subdev_id *remote_id; + int remote_pad; +}; + +struct internal_pad { + bool devnode; /* does this pad link to a device node */ + struct internal_link link[IMX_MEDIA_MAX_LINKS]; +}; + +static const struct internal_subdev { + const struct internal_subdev_id *id; + struct internal_pad pad[IMX_MEDIA_MAX_PADS]; + int num_sink_pads; + int num_src_pads; +} internal_subdev[num_isd] = { + [isd_csi0] = { + .id = &isd_id[isd_csi0], + .num_sink_pads = CSI_NUM_SINK_PADS, + .num_src_pads = CSI_NUM_SRC_PADS, + .pad[CSI_SRC_PAD_DIRECT] = { + .link = { + { + .remote_id = &isd_id[isd_ic_prp], + .remote_pad = PRP_SINK_PAD, + }, { + .remote_id = &isd_id[isd_vdic], + .remote_pad = VDIC_SINK_PAD_DIRECT, + }, + }, + }, + .pad[CSI_SRC_PAD_IDMAC] = { + .devnode = true, + }, + }, + + [isd_csi1] = { + .id = &isd_id[isd_csi1], + .num_sink_pads = CSI_NUM_SINK_PADS, + .num_src_pads = CSI_NUM_SRC_PADS, + .pad[CSI_SRC_PAD_DIRECT] = { + .link = { + { + .remote_id = &isd_id[isd_ic_prp], + .remote_pad = PRP_SINK_PAD, + }, { + .remote_id = &isd_id[isd_vdic], + .remote_pad = VDIC_SINK_PAD_DIRECT, + }, + }, + }, + .pad[CSI_SRC_PAD_IDMAC] = { + .devnode = true, + }, + }, + + [isd_vdic] = { + .id = &isd_id[isd_vdic], + .num_sink_pads = VDIC_NUM_SINK_PADS, + .num_src_pads = VDIC_NUM_SRC_PADS, + .pad[VDIC_SINK_PAD_IDMAC] = { + .devnode = true, + }, + .pad[VDIC_SRC_PAD_DIRECT] = { + .link = { + { + .remote_id = &isd_id[isd_ic_prp], + .remote_pad = PRP_SINK_PAD, + }, + }, + }, + }, + + [isd_ic_prp] = { + .id = &isd_id[isd_ic_prp], + .num_sink_pads = PRP_NUM_SINK_PADS, + .num_src_pads = PRP_NUM_SRC_PADS, + .pad[PRP_SRC_PAD_PRPENC] = { + .link = { + { + .remote_id = &isd_id[isd_ic_prpenc], + .remote_pad = 0, + }, + }, + }, + .pad[PRP_SRC_PAD_PRPVF] = { + .link = { + { + .remote_id = &isd_id[isd_ic_prpvf], + .remote_pad = 0, + }, + }, + }, + }, + + [isd_ic_prpenc] = { + .id = &isd_id[isd_ic_prpenc], + .num_sink_pads = PRPENCVF_NUM_SINK_PADS, + .num_src_pads = PRPENCVF_NUM_SRC_PADS, + .pad[PRPENCVF_SRC_PAD] = { + .devnode = true, + }, + }, + + [isd_ic_prpvf] = { + .id = &isd_id[isd_ic_prpvf], + .num_sink_pads = PRPENCVF_NUM_SINK_PADS, + .num_src_pads = PRPENCVF_NUM_SRC_PADS, + .pad[PRPENCVF_SRC_PAD] = { + .devnode = true, + }, + }, +}; + +/* form a device name given a group id and ipu id */ +static inline void isd_id_to_devname(char *devname, int sz, + const struct internal_subdev_id *id, + int ipu_id) +{ + int pdev_id = ipu_id * num_isd + id->index; + + snprintf(devname, sz, "%s.%d", id->name, pdev_id); +} + +/* adds the links from given internal subdev */ +static int add_internal_links(struct imx_media_dev *imxmd, + const struct internal_subdev *isd, + struct imx_media_subdev *imxsd, + int ipu_id) +{ + int i, num_pads, ret; + + num_pads = isd->num_sink_pads + isd->num_src_pads; + + for (i = 0; i < num_pads; i++) { + const struct internal_pad *intpad = &isd->pad[i]; + struct imx_media_pad *pad = &imxsd->pad[i]; + int j; + + /* init the pad flags for this internal subdev */ + pad->pad.flags = (i < isd->num_sink_pads) ? + MEDIA_PAD_FL_SINK : MEDIA_PAD_FL_SOURCE; + /* export devnode pad flag to the subdevs */ + pad->devnode = intpad->devnode; + + for (j = 0; ; j++) { + const struct internal_link *link; + char remote_devname[32]; + + link = &intpad->link[j]; + + if (!link->remote_id) + break; + + isd_id_to_devname(remote_devname, + sizeof(remote_devname), + link->remote_id, ipu_id); + + ret = imx_media_add_pad_link(imxmd, pad, + NULL, remote_devname, + i, link->remote_pad); + if (ret) + return ret; + } + } + + return 0; +} + +/* register an internal subdev as a platform device */ +static struct imx_media_subdev * +add_internal_subdev(struct imx_media_dev *imxmd, + const struct internal_subdev *isd, + int ipu_id) +{ + struct imx_media_internal_sd_platformdata pdata; + struct platform_device_info pdevinfo = {0}; + struct imx_media_subdev *imxsd; + struct platform_device *pdev; + + pdata.grp_id = isd->id->grp_id; + + /* the id of IPU this subdev will control */ + pdata.ipu_id = ipu_id; + + /* create subdev name */ + imx_media_grp_id_to_sd_name(pdata.sd_name, sizeof(pdata.sd_name), + pdata.grp_id, ipu_id); + + pdevinfo.name = isd->id->name; + pdevinfo.id = ipu_id * num_isd + isd->id->index; + pdevinfo.parent = imxmd->md.dev; + pdevinfo.data = &pdata; + pdevinfo.size_data = sizeof(pdata); + pdevinfo.dma_mask = DMA_BIT_MASK(32); + + pdev = platform_device_register_full(&pdevinfo); + if (IS_ERR(pdev)) + return ERR_CAST(pdev); + + imxsd = imx_media_add_async_subdev(imxmd, NULL, pdev); + if (IS_ERR(imxsd)) + return imxsd; + + imxsd->num_sink_pads = isd->num_sink_pads; + imxsd->num_src_pads = isd->num_src_pads; + + return imxsd; +} + +/* adds the internal subdevs in one ipu */ +static int add_ipu_internal_subdevs(struct imx_media_dev *imxmd, + struct imx_media_subdev *csi0, + struct imx_media_subdev *csi1, + int ipu_id) +{ + enum isd_enum i; + int ret; + + for (i = 0; i < num_isd; i++) { + const struct internal_subdev *isd = &internal_subdev[i]; + struct imx_media_subdev *imxsd; + + /* + * the CSIs are represented in the device-tree, so those + * devices are added already, and are added to the async + * subdev list by of_parse_subdev(), so we are given those + * subdevs as csi0 and csi1. + */ + switch (isd->id->grp_id) { + case IMX_MEDIA_GRP_ID_CSI0: + imxsd = csi0; + break; + case IMX_MEDIA_GRP_ID_CSI1: + imxsd = csi1; + break; + default: + imxsd = add_internal_subdev(imxmd, isd, ipu_id); + break; + } + + if (IS_ERR(imxsd)) + return PTR_ERR(imxsd); + + /* add the links from this subdev */ + if (imxsd) { + ret = add_internal_links(imxmd, isd, imxsd, ipu_id); + if (ret) + return ret; + } + } + + return 0; +} + +int imx_media_add_internal_subdevs(struct imx_media_dev *imxmd, + struct imx_media_subdev *csi[4]) +{ + int ret; + + ret = add_ipu_internal_subdevs(imxmd, csi[0], csi[1], 0); + if (ret) + goto remove; + + ret = add_ipu_internal_subdevs(imxmd, csi[2], csi[3], 1); + if (ret) + goto remove; + + return 0; + +remove: + imx_media_remove_internal_subdevs(imxmd); + return ret; +} + +void imx_media_remove_internal_subdevs(struct imx_media_dev *imxmd) +{ + struct imx_media_subdev *imxsd; + int i; + + for (i = 0; i < imxmd->subdev_notifier.num_subdevs; i++) { + imxsd = &imxmd->subdev[i]; + if (!imxsd->pdev) + continue; + platform_device_unregister(imxsd->pdev); + } +} diff --git a/drivers/staging/media/imx/imx-media-of.c b/drivers/staging/media/imx/imx-media-of.c new file mode 100644 index 000000000000..b026fe66467c --- /dev/null +++ b/drivers/staging/media/imx/imx-media-of.c @@ -0,0 +1,270 @@ +/* + * Media driver for Freescale i.MX5/6 SOC + * + * Open Firmware parsing. + * + * Copyright (c) 2016 Mentor Graphics Inc. + * + * 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; either version 2 of the License, or + * (at your option) any later version. + */ +#include <linux/of_platform.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-subdev.h> +#include <media/videobuf2-dma-contig.h> +#include <linux/of_graph.h> +#include <video/imx-ipu-v3.h> +#include "imx-media.h" + +static int of_add_pad_link(struct imx_media_dev *imxmd, + struct imx_media_pad *pad, + struct device_node *local_sd_node, + struct device_node *remote_sd_node, + int local_pad, int remote_pad) +{ + dev_dbg(imxmd->md.dev, "%s: adding %s:%d -> %s:%d\n", __func__, + local_sd_node->name, local_pad, + remote_sd_node->name, remote_pad); + + return imx_media_add_pad_link(imxmd, pad, remote_sd_node, NULL, + local_pad, remote_pad); +} + +static void of_parse_sensor(struct imx_media_dev *imxmd, + struct imx_media_subdev *sensor, + struct device_node *sensor_np) +{ + struct device_node *endpoint; + + endpoint = of_graph_get_next_endpoint(sensor_np, NULL); + if (endpoint) { + v4l2_fwnode_endpoint_parse(of_fwnode_handle(endpoint), + &sensor->sensor_ep); + of_node_put(endpoint); + } +} + +static int of_get_port_count(const struct device_node *np) +{ + struct device_node *ports, *child; + int num = 0; + + /* check if this node has a ports subnode */ + ports = of_get_child_by_name(np, "ports"); + if (ports) + np = ports; + + for_each_child_of_node(np, child) + if (of_node_cmp(child->name, "port") == 0) + num++; + + of_node_put(ports); + return num; +} + +/* + * find the remote device node and remote port id (remote pad #) + * given local endpoint node + */ +static void of_get_remote_pad(struct device_node *epnode, + struct device_node **remote_node, + int *remote_pad) +{ + struct device_node *rp, *rpp; + struct device_node *remote; + + rp = of_graph_get_remote_port(epnode); + rpp = of_graph_get_remote_port_parent(epnode); + + if (of_device_is_compatible(rpp, "fsl,imx6q-ipu")) { + /* the remote is one of the CSI ports */ + remote = rp; + *remote_pad = 0; + of_node_put(rpp); + } else { + remote = rpp; + if (of_property_read_u32(rp, "reg", remote_pad)) + *remote_pad = 0; + of_node_put(rp); + } + + if (!of_device_is_available(remote)) { + of_node_put(remote); + *remote_node = NULL; + } else { + *remote_node = remote; + } +} + +static struct imx_media_subdev * +of_parse_subdev(struct imx_media_dev *imxmd, struct device_node *sd_np, + bool is_csi_port) +{ + struct imx_media_subdev *imxsd; + int i, num_pads, ret; + + if (!of_device_is_available(sd_np)) { + dev_dbg(imxmd->md.dev, "%s: %s not enabled\n", __func__, + sd_np->name); + return NULL; + } + + /* register this subdev with async notifier */ + imxsd = imx_media_add_async_subdev(imxmd, sd_np, NULL); + if (IS_ERR_OR_NULL(imxsd)) + return imxsd; + + if (is_csi_port) { + /* + * the ipu-csi has one sink port and two source ports. + * The source ports are not represented in the device tree, + * but are described by the internal pads and links later. + */ + num_pads = CSI_NUM_PADS; + imxsd->num_sink_pads = CSI_NUM_SINK_PADS; + } else if (of_device_is_compatible(sd_np, "fsl,imx6-mipi-csi2")) { + num_pads = of_get_port_count(sd_np); + /* the mipi csi2 receiver has only one sink port */ + imxsd->num_sink_pads = 1; + } else if (of_device_is_compatible(sd_np, "video-mux")) { + num_pads = of_get_port_count(sd_np); + /* for the video mux, all but the last port are sinks */ + imxsd->num_sink_pads = num_pads - 1; + } else { + num_pads = of_get_port_count(sd_np); + if (num_pads != 1) { + dev_warn(imxmd->md.dev, + "%s: unknown device %s with %d ports\n", + __func__, sd_np->name, num_pads); + return NULL; + } + + /* + * we got to this node from this single source port, + * there are no sink pads. + */ + imxsd->num_sink_pads = 0; + } + + if (imxsd->num_sink_pads >= num_pads) + return ERR_PTR(-EINVAL); + + imxsd->num_src_pads = num_pads - imxsd->num_sink_pads; + + dev_dbg(imxmd->md.dev, "%s: %s has %d pads (%d sink, %d src)\n", + __func__, sd_np->name, num_pads, + imxsd->num_sink_pads, imxsd->num_src_pads); + + /* + * With no sink, this subdev node is the original source + * of video, parse it's media bus for use by the pipeline. + */ + if (imxsd->num_sink_pads == 0) + of_parse_sensor(imxmd, imxsd, sd_np); + + for (i = 0; i < num_pads; i++) { + struct device_node *epnode = NULL, *port, *remote_np; + struct imx_media_subdev *remote_imxsd; + struct imx_media_pad *pad; + int remote_pad; + + /* init this pad */ + pad = &imxsd->pad[i]; + pad->pad.flags = (i < imxsd->num_sink_pads) ? + MEDIA_PAD_FL_SINK : MEDIA_PAD_FL_SOURCE; + + if (is_csi_port) + port = (i < imxsd->num_sink_pads) ? sd_np : NULL; + else + port = of_graph_get_port_by_id(sd_np, i); + if (!port) + continue; + + for_each_child_of_node(port, epnode) { + of_get_remote_pad(epnode, &remote_np, &remote_pad); + if (!remote_np) + continue; + + ret = of_add_pad_link(imxmd, pad, sd_np, remote_np, + i, remote_pad); + if (ret) { + imxsd = ERR_PTR(ret); + break; + } + + if (i < imxsd->num_sink_pads) { + /* follow sink endpoints upstream */ + remote_imxsd = of_parse_subdev(imxmd, + remote_np, + false); + if (IS_ERR(remote_imxsd)) { + imxsd = remote_imxsd; + break; + } + } + + of_node_put(remote_np); + } + + if (port != sd_np) + of_node_put(port); + if (IS_ERR(imxsd)) { + of_node_put(remote_np); + of_node_put(epnode); + break; + } + } + + return imxsd; +} + +int imx_media_of_parse(struct imx_media_dev *imxmd, + struct imx_media_subdev *(*csi)[4], + struct device_node *np) +{ + struct imx_media_subdev *lcsi; + struct device_node *csi_np; + u32 ipu_id, csi_id; + int i, ret; + + for (i = 0; ; i++) { + csi_np = of_parse_phandle(np, "ports", i); + if (!csi_np) + break; + + lcsi = of_parse_subdev(imxmd, csi_np, true); + if (IS_ERR(lcsi)) { + ret = PTR_ERR(lcsi); + goto err_put; + } + + ret = of_property_read_u32(csi_np, "reg", &csi_id); + if (ret) { + dev_err(imxmd->md.dev, + "%s: csi port missing reg property!\n", + __func__); + goto err_put; + } + + ipu_id = of_alias_get_id(csi_np->parent, "ipu"); + of_node_put(csi_np); + + if (ipu_id > 1 || csi_id > 1) { + dev_err(imxmd->md.dev, + "%s: invalid ipu/csi id (%u/%u)\n", + __func__, ipu_id, csi_id); + return -EINVAL; + } + + (*csi)[ipu_id * 2 + csi_id] = lcsi; + } + + return 0; +err_put: + of_node_put(csi_np); + return ret; +} diff --git a/drivers/staging/media/imx/imx-media-utils.c b/drivers/staging/media/imx/imx-media-utils.c new file mode 100644 index 000000000000..59523872a886 --- /dev/null +++ b/drivers/staging/media/imx/imx-media-utils.c @@ -0,0 +1,896 @@ +/* + * V4L2 Media Controller Driver for Freescale i.MX5/6 SOC + * + * Copyright (c) 2016 Mentor Graphics Inc. + * + * 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; either version 2 of the License, or + * (at your option) any later version. + */ +#include <linux/module.h> +#include "imx-media.h" + +/* + * List of supported pixel formats for the subdevs. + * + * In all of these tables, the non-mbus formats (with no + * mbus codes) must all fall at the end of the table. + */ + +static const struct imx_media_pixfmt yuv_formats[] = { + { + .fourcc = V4L2_PIX_FMT_UYVY, + .codes = { + MEDIA_BUS_FMT_UYVY8_2X8, + MEDIA_BUS_FMT_UYVY8_1X16 + }, + .cs = IPUV3_COLORSPACE_YUV, + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_YUYV, + .codes = { + MEDIA_BUS_FMT_YUYV8_2X8, + MEDIA_BUS_FMT_YUYV8_1X16 + }, + .cs = IPUV3_COLORSPACE_YUV, + .bpp = 16, + }, + /*** + * non-mbus YUV formats start here. NOTE! when adding non-mbus + * formats, NUM_NON_MBUS_YUV_FORMATS must be updated below. + ***/ + { + .fourcc = V4L2_PIX_FMT_YUV420, + .cs = IPUV3_COLORSPACE_YUV, + .bpp = 12, + .planar = true, + }, { + .fourcc = V4L2_PIX_FMT_YVU420, + .cs = IPUV3_COLORSPACE_YUV, + .bpp = 12, + .planar = true, + }, { + .fourcc = V4L2_PIX_FMT_YUV422P, + .cs = IPUV3_COLORSPACE_YUV, + .bpp = 16, + .planar = true, + }, { + .fourcc = V4L2_PIX_FMT_NV12, + .cs = IPUV3_COLORSPACE_YUV, + .bpp = 12, + .planar = true, + }, { + .fourcc = V4L2_PIX_FMT_NV16, + .cs = IPUV3_COLORSPACE_YUV, + .bpp = 16, + .planar = true, + }, +}; + +#define NUM_NON_MBUS_YUV_FORMATS 5 +#define NUM_YUV_FORMATS ARRAY_SIZE(yuv_formats) +#define NUM_MBUS_YUV_FORMATS (NUM_YUV_FORMATS - NUM_NON_MBUS_YUV_FORMATS) + +static const struct imx_media_pixfmt rgb_formats[] = { + { + .fourcc = V4L2_PIX_FMT_RGB565, + .codes = {MEDIA_BUS_FMT_RGB565_2X8_LE}, + .cs = IPUV3_COLORSPACE_RGB, + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_RGB24, + .codes = { + MEDIA_BUS_FMT_RGB888_1X24, + MEDIA_BUS_FMT_RGB888_2X12_LE + }, + .cs = IPUV3_COLORSPACE_RGB, + .bpp = 24, + }, { + .fourcc = V4L2_PIX_FMT_RGB32, + .codes = {MEDIA_BUS_FMT_ARGB8888_1X32}, + .cs = IPUV3_COLORSPACE_RGB, + .bpp = 32, + .ipufmt = true, + }, + /*** raw bayer formats start here ***/ + { + .fourcc = V4L2_PIX_FMT_SBGGR8, + .codes = {MEDIA_BUS_FMT_SBGGR8_1X8}, + .cs = IPUV3_COLORSPACE_RGB, + .bpp = 8, + .bayer = true, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG8, + .codes = {MEDIA_BUS_FMT_SGBRG8_1X8}, + .cs = IPUV3_COLORSPACE_RGB, + .bpp = 8, + .bayer = true, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG8, + .codes = {MEDIA_BUS_FMT_SGRBG8_1X8}, + .cs = IPUV3_COLORSPACE_RGB, + .bpp = 8, + .bayer = true, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB8, + .codes = {MEDIA_BUS_FMT_SRGGB8_1X8}, + .cs = IPUV3_COLORSPACE_RGB, + .bpp = 8, + .bayer = true, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR16, + .codes = { + MEDIA_BUS_FMT_SBGGR10_1X10, + MEDIA_BUS_FMT_SBGGR12_1X12, + MEDIA_BUS_FMT_SBGGR14_1X14, + MEDIA_BUS_FMT_SBGGR16_1X16 + }, + .cs = IPUV3_COLORSPACE_RGB, + .bpp = 16, + .bayer = true, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG16, + .codes = { + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SGBRG12_1X12, + MEDIA_BUS_FMT_SGBRG14_1X14, + MEDIA_BUS_FMT_SGBRG16_1X16, + }, + .cs = IPUV3_COLORSPACE_RGB, + .bpp = 16, + .bayer = true, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG16, + .codes = { + MEDIA_BUS_FMT_SGRBG10_1X10, + MEDIA_BUS_FMT_SGRBG12_1X12, + MEDIA_BUS_FMT_SGRBG14_1X14, + MEDIA_BUS_FMT_SGRBG16_1X16, + }, + .cs = IPUV3_COLORSPACE_RGB, + .bpp = 16, + .bayer = true, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB16, + .codes = { + MEDIA_BUS_FMT_SRGGB10_1X10, + MEDIA_BUS_FMT_SRGGB12_1X12, + MEDIA_BUS_FMT_SRGGB14_1X14, + MEDIA_BUS_FMT_SRGGB16_1X16, + }, + .cs = IPUV3_COLORSPACE_RGB, + .bpp = 16, + .bayer = true, + }, + /*** + * non-mbus RGB formats start here. NOTE! when adding non-mbus + * formats, NUM_NON_MBUS_RGB_FORMATS must be updated below. + ***/ + { + .fourcc = V4L2_PIX_FMT_BGR24, + .cs = IPUV3_COLORSPACE_RGB, + .bpp = 24, + }, { + .fourcc = V4L2_PIX_FMT_BGR32, + .cs = IPUV3_COLORSPACE_RGB, + .bpp = 32, + }, +}; + +#define NUM_NON_MBUS_RGB_FORMATS 2 +#define NUM_RGB_FORMATS ARRAY_SIZE(rgb_formats) +#define NUM_MBUS_RGB_FORMATS (NUM_RGB_FORMATS - NUM_NON_MBUS_RGB_FORMATS) + +static const struct imx_media_pixfmt ipu_yuv_formats[] = { + { + .fourcc = V4L2_PIX_FMT_YUV32, + .codes = {MEDIA_BUS_FMT_AYUV8_1X32}, + .cs = IPUV3_COLORSPACE_YUV, + .bpp = 32, + .ipufmt = true, + }, +}; + +#define NUM_IPU_YUV_FORMATS ARRAY_SIZE(ipu_yuv_formats) + +static const struct imx_media_pixfmt ipu_rgb_formats[] = { + { + .fourcc = V4L2_PIX_FMT_RGB32, + .codes = {MEDIA_BUS_FMT_ARGB8888_1X32}, + .cs = IPUV3_COLORSPACE_RGB, + .bpp = 32, + .ipufmt = true, + }, +}; + +#define NUM_IPU_RGB_FORMATS ARRAY_SIZE(ipu_rgb_formats) + +static void init_mbus_colorimetry(struct v4l2_mbus_framefmt *mbus, + const struct imx_media_pixfmt *fmt) +{ + mbus->colorspace = (fmt->cs == IPUV3_COLORSPACE_RGB) ? + V4L2_COLORSPACE_SRGB : V4L2_COLORSPACE_SMPTE170M; + mbus->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(mbus->colorspace); + mbus->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(mbus->colorspace); + mbus->quantization = + V4L2_MAP_QUANTIZATION_DEFAULT(fmt->cs == IPUV3_COLORSPACE_RGB, + mbus->colorspace, + mbus->ycbcr_enc); +} + +static const struct imx_media_pixfmt *find_format(u32 fourcc, + u32 code, + enum codespace_sel cs_sel, + bool allow_non_mbus, + bool allow_bayer) +{ + const struct imx_media_pixfmt *array, *fmt, *ret = NULL; + u32 array_size; + int i, j; + + switch (cs_sel) { + case CS_SEL_YUV: + array_size = NUM_YUV_FORMATS; + array = yuv_formats; + break; + case CS_SEL_RGB: + array_size = NUM_RGB_FORMATS; + array = rgb_formats; + break; + case CS_SEL_ANY: + array_size = NUM_YUV_FORMATS + NUM_RGB_FORMATS; + array = yuv_formats; + break; + default: + return NULL; + } + + for (i = 0; i < array_size; i++) { + if (cs_sel == CS_SEL_ANY && i >= NUM_YUV_FORMATS) + fmt = &rgb_formats[i - NUM_YUV_FORMATS]; + else + fmt = &array[i]; + + if ((!allow_non_mbus && fmt->codes[0] == 0) || + (!allow_bayer && fmt->bayer)) + continue; + + if (fourcc && fmt->fourcc == fourcc) { + ret = fmt; + goto out; + } + + for (j = 0; code && fmt->codes[j]; j++) { + if (code == fmt->codes[j]) { + ret = fmt; + goto out; + } + } + } + +out: + return ret; +} + +static int enum_format(u32 *fourcc, u32 *code, u32 index, + enum codespace_sel cs_sel, + bool allow_non_mbus, + bool allow_bayer) +{ + const struct imx_media_pixfmt *fmt; + u32 mbus_yuv_sz = NUM_MBUS_YUV_FORMATS; + u32 mbus_rgb_sz = NUM_MBUS_RGB_FORMATS; + u32 yuv_sz = NUM_YUV_FORMATS; + u32 rgb_sz = NUM_RGB_FORMATS; + + switch (cs_sel) { + case CS_SEL_YUV: + if (index >= yuv_sz || + (!allow_non_mbus && index >= mbus_yuv_sz)) + return -EINVAL; + fmt = &yuv_formats[index]; + break; + case CS_SEL_RGB: + if (index >= rgb_sz || + (!allow_non_mbus && index >= mbus_rgb_sz)) + return -EINVAL; + fmt = &rgb_formats[index]; + if (!allow_bayer && fmt->bayer) + return -EINVAL; + break; + case CS_SEL_ANY: + if (!allow_non_mbus) { + if (index >= mbus_yuv_sz) { + index -= mbus_yuv_sz; + if (index >= mbus_rgb_sz) + return -EINVAL; + fmt = &rgb_formats[index]; + if (!allow_bayer && fmt->bayer) + return -EINVAL; + } else { + fmt = &yuv_formats[index]; + } + } else { + if (index >= yuv_sz + rgb_sz) + return -EINVAL; + if (index >= yuv_sz) { + fmt = &rgb_formats[index - yuv_sz]; + if (!allow_bayer && fmt->bayer) + return -EINVAL; + } else { + fmt = &yuv_formats[index]; + } + } + break; + default: + return -EINVAL; + } + + if (fourcc) + *fourcc = fmt->fourcc; + if (code) + *code = fmt->codes[0]; + + return 0; +} + +const struct imx_media_pixfmt * +imx_media_find_format(u32 fourcc, enum codespace_sel cs_sel, bool allow_bayer) +{ + return find_format(fourcc, 0, cs_sel, true, allow_bayer); +} +EXPORT_SYMBOL_GPL(imx_media_find_format); + +int imx_media_enum_format(u32 *fourcc, u32 index, enum codespace_sel cs_sel) +{ + return enum_format(fourcc, NULL, index, cs_sel, true, false); +} +EXPORT_SYMBOL_GPL(imx_media_enum_format); + +const struct imx_media_pixfmt * +imx_media_find_mbus_format(u32 code, enum codespace_sel cs_sel, + bool allow_bayer) +{ + return find_format(0, code, cs_sel, false, allow_bayer); +} +EXPORT_SYMBOL_GPL(imx_media_find_mbus_format); + +int imx_media_enum_mbus_format(u32 *code, u32 index, enum codespace_sel cs_sel, + bool allow_bayer) +{ + return enum_format(NULL, code, index, cs_sel, false, allow_bayer); +} +EXPORT_SYMBOL_GPL(imx_media_enum_mbus_format); + +const struct imx_media_pixfmt * +imx_media_find_ipu_format(u32 code, enum codespace_sel cs_sel) +{ + const struct imx_media_pixfmt *array, *fmt, *ret = NULL; + u32 array_size; + int i, j; + + switch (cs_sel) { + case CS_SEL_YUV: + array_size = NUM_IPU_YUV_FORMATS; + array = ipu_yuv_formats; + break; + case CS_SEL_RGB: + array_size = NUM_IPU_RGB_FORMATS; + array = ipu_rgb_formats; + break; + case CS_SEL_ANY: + array_size = NUM_IPU_YUV_FORMATS + NUM_IPU_RGB_FORMATS; + array = ipu_yuv_formats; + break; + default: + return NULL; + } + + for (i = 0; i < array_size; i++) { + if (cs_sel == CS_SEL_ANY && i >= NUM_IPU_YUV_FORMATS) + fmt = &ipu_rgb_formats[i - NUM_IPU_YUV_FORMATS]; + else + fmt = &array[i]; + + for (j = 0; code && fmt->codes[j]; j++) { + if (code == fmt->codes[j]) { + ret = fmt; + goto out; + } + } + } + +out: + return ret; +} +EXPORT_SYMBOL_GPL(imx_media_find_ipu_format); + +int imx_media_enum_ipu_format(u32 *code, u32 index, enum codespace_sel cs_sel) +{ + switch (cs_sel) { + case CS_SEL_YUV: + if (index >= NUM_IPU_YUV_FORMATS) + return -EINVAL; + *code = ipu_yuv_formats[index].codes[0]; + break; + case CS_SEL_RGB: + if (index >= NUM_IPU_RGB_FORMATS) + return -EINVAL; + *code = ipu_rgb_formats[index].codes[0]; + break; + case CS_SEL_ANY: + if (index >= NUM_IPU_YUV_FORMATS + NUM_IPU_RGB_FORMATS) + return -EINVAL; + if (index >= NUM_IPU_YUV_FORMATS) { + index -= NUM_IPU_YUV_FORMATS; + *code = ipu_rgb_formats[index].codes[0]; + } else { + *code = ipu_yuv_formats[index].codes[0]; + } + break; + default: + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL_GPL(imx_media_enum_ipu_format); + +int imx_media_init_mbus_fmt(struct v4l2_mbus_framefmt *mbus, + u32 width, u32 height, u32 code, u32 field, + const struct imx_media_pixfmt **cc) +{ + const struct imx_media_pixfmt *lcc; + + mbus->width = width; + mbus->height = height; + mbus->field = field; + if (code == 0) + imx_media_enum_mbus_format(&code, 0, CS_SEL_YUV, false); + lcc = imx_media_find_mbus_format(code, CS_SEL_ANY, false); + if (!lcc) { + lcc = imx_media_find_ipu_format(code, CS_SEL_ANY); + if (!lcc) + return -EINVAL; + } + + mbus->code = code; + init_mbus_colorimetry(mbus, lcc); + if (cc) + *cc = lcc; + + return 0; +} +EXPORT_SYMBOL_GPL(imx_media_init_mbus_fmt); + +/* + * Check whether the field and colorimetry parameters in tryfmt are + * uninitialized, and if so fill them with the values from fmt, + * or if tryfmt->colorspace has been initialized, all the default + * colorimetry params can be derived from tryfmt->colorspace. + * + * tryfmt->code must be set on entry. + * + * If this format is destined to be routed through the Image Converter, + * quantization and Y`CbCr encoding must be fixed. The IC expects and + * produces fixed quantization and Y`CbCr encoding at its input and output + * (full range for RGB, limited range for YUV, and V4L2_YCBCR_ENC_601). + */ +void imx_media_fill_default_mbus_fields(struct v4l2_mbus_framefmt *tryfmt, + struct v4l2_mbus_framefmt *fmt, + bool ic_route) +{ + const struct imx_media_pixfmt *cc; + bool is_rgb = false; + + cc = imx_media_find_mbus_format(tryfmt->code, CS_SEL_ANY, true); + if (!cc) + cc = imx_media_find_ipu_format(tryfmt->code, CS_SEL_ANY); + if (cc && cc->cs != IPUV3_COLORSPACE_YUV) + is_rgb = true; + + /* fill field if necessary */ + if (tryfmt->field == V4L2_FIELD_ANY) + tryfmt->field = fmt->field; + + /* fill colorimetry if necessary */ + if (tryfmt->colorspace == V4L2_COLORSPACE_DEFAULT) { + tryfmt->colorspace = fmt->colorspace; + tryfmt->xfer_func = fmt->xfer_func; + tryfmt->ycbcr_enc = fmt->ycbcr_enc; + tryfmt->quantization = fmt->quantization; + } else { + if (tryfmt->xfer_func == V4L2_XFER_FUNC_DEFAULT) { + tryfmt->xfer_func = + V4L2_MAP_XFER_FUNC_DEFAULT(tryfmt->colorspace); + } + if (tryfmt->ycbcr_enc == V4L2_YCBCR_ENC_DEFAULT) { + tryfmt->ycbcr_enc = + V4L2_MAP_YCBCR_ENC_DEFAULT(tryfmt->colorspace); + } + if (tryfmt->quantization == V4L2_QUANTIZATION_DEFAULT) { + tryfmt->quantization = + V4L2_MAP_QUANTIZATION_DEFAULT( + is_rgb, tryfmt->colorspace, + tryfmt->ycbcr_enc); + } + } + + if (ic_route) { + tryfmt->quantization = is_rgb ? + V4L2_QUANTIZATION_FULL_RANGE : + V4L2_QUANTIZATION_LIM_RANGE; + tryfmt->ycbcr_enc = V4L2_YCBCR_ENC_601; + } +} +EXPORT_SYMBOL_GPL(imx_media_fill_default_mbus_fields); + +int imx_media_mbus_fmt_to_pix_fmt(struct v4l2_pix_format *pix, + struct v4l2_mbus_framefmt *mbus, + const struct imx_media_pixfmt *cc) +{ + u32 stride; + + if (!cc) { + cc = imx_media_find_ipu_format(mbus->code, CS_SEL_ANY); + if (!cc) + cc = imx_media_find_mbus_format(mbus->code, CS_SEL_ANY, + true); + if (!cc) + return -EINVAL; + } + + /* + * TODO: the IPU currently does not support the AYUV32 format, + * so until it does convert to a supported YUV format. + */ + if (cc->ipufmt && cc->cs == IPUV3_COLORSPACE_YUV) { + u32 code; + + imx_media_enum_mbus_format(&code, 0, CS_SEL_YUV, false); + cc = imx_media_find_mbus_format(code, CS_SEL_YUV, false); + } + + stride = cc->planar ? mbus->width : (mbus->width * cc->bpp) >> 3; + + pix->width = mbus->width; + pix->height = mbus->height; + pix->pixelformat = cc->fourcc; + pix->colorspace = mbus->colorspace; + pix->xfer_func = mbus->xfer_func; + pix->ycbcr_enc = mbus->ycbcr_enc; + pix->quantization = mbus->quantization; + pix->field = mbus->field; + pix->bytesperline = stride; + pix->sizeimage = (pix->width * pix->height * cc->bpp) >> 3; + + return 0; +} +EXPORT_SYMBOL_GPL(imx_media_mbus_fmt_to_pix_fmt); + +int imx_media_mbus_fmt_to_ipu_image(struct ipu_image *image, + struct v4l2_mbus_framefmt *mbus) +{ + int ret; + + memset(image, 0, sizeof(*image)); + + ret = imx_media_mbus_fmt_to_pix_fmt(&image->pix, mbus, NULL); + if (ret) + return ret; + + image->rect.width = mbus->width; + image->rect.height = mbus->height; + + return 0; +} +EXPORT_SYMBOL_GPL(imx_media_mbus_fmt_to_ipu_image); + +int imx_media_ipu_image_to_mbus_fmt(struct v4l2_mbus_framefmt *mbus, + struct ipu_image *image) +{ + const struct imx_media_pixfmt *fmt; + + fmt = imx_media_find_format(image->pix.pixelformat, CS_SEL_ANY, true); + if (!fmt) + return -EINVAL; + + memset(mbus, 0, sizeof(*mbus)); + mbus->width = image->pix.width; + mbus->height = image->pix.height; + mbus->code = fmt->codes[0]; + mbus->field = image->pix.field; + mbus->colorspace = image->pix.colorspace; + mbus->xfer_func = image->pix.xfer_func; + mbus->ycbcr_enc = image->pix.ycbcr_enc; + mbus->quantization = image->pix.quantization; + + return 0; +} +EXPORT_SYMBOL_GPL(imx_media_ipu_image_to_mbus_fmt); + +void imx_media_free_dma_buf(struct imx_media_dev *imxmd, + struct imx_media_dma_buf *buf) +{ + if (buf->virt) + dma_free_coherent(imxmd->md.dev, buf->len, + buf->virt, buf->phys); + + buf->virt = NULL; + buf->phys = 0; +} +EXPORT_SYMBOL_GPL(imx_media_free_dma_buf); + +int imx_media_alloc_dma_buf(struct imx_media_dev *imxmd, + struct imx_media_dma_buf *buf, + int size) +{ + imx_media_free_dma_buf(imxmd, buf); + + buf->len = PAGE_ALIGN(size); + buf->virt = dma_alloc_coherent(imxmd->md.dev, buf->len, &buf->phys, + GFP_DMA | GFP_KERNEL); + if (!buf->virt) { + dev_err(imxmd->md.dev, "failed to alloc dma buffer\n"); + return -ENOMEM; + } + + return 0; +} +EXPORT_SYMBOL_GPL(imx_media_alloc_dma_buf); + +/* form a subdev name given a group id and ipu id */ +void imx_media_grp_id_to_sd_name(char *sd_name, int sz, u32 grp_id, int ipu_id) +{ + int id; + + switch (grp_id) { + case IMX_MEDIA_GRP_ID_CSI0...IMX_MEDIA_GRP_ID_CSI1: + id = (grp_id >> IMX_MEDIA_GRP_ID_CSI_BIT) - 1; + snprintf(sd_name, sz, "ipu%d_csi%d", ipu_id + 1, id); + break; + case IMX_MEDIA_GRP_ID_VDIC: + snprintf(sd_name, sz, "ipu%d_vdic", ipu_id + 1); + break; + case IMX_MEDIA_GRP_ID_IC_PRP: + snprintf(sd_name, sz, "ipu%d_ic_prp", ipu_id + 1); + break; + case IMX_MEDIA_GRP_ID_IC_PRPENC: + snprintf(sd_name, sz, "ipu%d_ic_prpenc", ipu_id + 1); + break; + case IMX_MEDIA_GRP_ID_IC_PRPVF: + snprintf(sd_name, sz, "ipu%d_ic_prpvf", ipu_id + 1); + break; + default: + break; + } +} +EXPORT_SYMBOL_GPL(imx_media_grp_id_to_sd_name); + +struct imx_media_subdev * +imx_media_find_subdev_by_sd(struct imx_media_dev *imxmd, + struct v4l2_subdev *sd) +{ + struct imx_media_subdev *imxsd; + int i; + + for (i = 0; i < imxmd->num_subdevs; i++) { + imxsd = &imxmd->subdev[i]; + if (sd == imxsd->sd) + return imxsd; + } + + return ERR_PTR(-ENODEV); +} +EXPORT_SYMBOL_GPL(imx_media_find_subdev_by_sd); + +struct imx_media_subdev * +imx_media_find_subdev_by_id(struct imx_media_dev *imxmd, u32 grp_id) +{ + struct imx_media_subdev *imxsd; + int i; + + for (i = 0; i < imxmd->num_subdevs; i++) { + imxsd = &imxmd->subdev[i]; + if (imxsd->sd && imxsd->sd->grp_id == grp_id) + return imxsd; + } + + return ERR_PTR(-ENODEV); +} +EXPORT_SYMBOL_GPL(imx_media_find_subdev_by_id); + +/* + * Adds a video device to the master video device list. This is called by + * an async subdev that owns a video device when it is registered. + */ +int imx_media_add_video_device(struct imx_media_dev *imxmd, + struct imx_media_video_dev *vdev) +{ + int vdev_idx, ret = 0; + + mutex_lock(&imxmd->mutex); + + vdev_idx = imxmd->num_vdevs; + if (vdev_idx >= IMX_MEDIA_MAX_VDEVS) { + dev_err(imxmd->md.dev, + "%s: too many video devices! can't add %s\n", + __func__, vdev->vfd->name); + ret = -ENOSPC; + goto out; + } + + imxmd->vdev[vdev_idx] = vdev; + imxmd->num_vdevs++; +out: + mutex_unlock(&imxmd->mutex); + return ret; +} +EXPORT_SYMBOL_GPL(imx_media_add_video_device); + +/* + * Search upstream or downstream for a subdevice in the current pipeline + * with given grp_id, starting from start_entity. Returns the subdev's + * source/sink pad that it was reached from. Must be called with + * mdev->graph_mutex held. + */ +static struct media_pad * +find_pipeline_pad(struct imx_media_dev *imxmd, + struct media_entity *start_entity, + u32 grp_id, bool upstream) +{ + struct media_entity *me = start_entity; + struct media_pad *pad = NULL; + struct v4l2_subdev *sd; + int i; + + for (i = 0; i < me->num_pads; i++) { + struct media_pad *spad = &me->pads[i]; + + if ((upstream && !(spad->flags & MEDIA_PAD_FL_SINK)) || + (!upstream && !(spad->flags & MEDIA_PAD_FL_SOURCE))) + continue; + + pad = media_entity_remote_pad(spad); + if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) + continue; + + sd = media_entity_to_v4l2_subdev(pad->entity); + if (sd->grp_id & grp_id) + return pad; + + return find_pipeline_pad(imxmd, pad->entity, grp_id, upstream); + } + + return NULL; +} + +/* + * Search upstream for a subdev in the current pipeline with + * given grp_id. Must be called with mdev->graph_mutex held. + */ +static struct v4l2_subdev * +find_upstream_subdev(struct imx_media_dev *imxmd, + struct media_entity *start_entity, + u32 grp_id) +{ + struct v4l2_subdev *sd; + struct media_pad *pad; + + if (is_media_entity_v4l2_subdev(start_entity)) { + sd = media_entity_to_v4l2_subdev(start_entity); + if (sd->grp_id & grp_id) + return sd; + } + + pad = find_pipeline_pad(imxmd, start_entity, grp_id, true); + + return pad ? media_entity_to_v4l2_subdev(pad->entity) : NULL; +} + + +/* + * Find the upstream mipi-csi2 virtual channel reached from the given + * start entity in the current pipeline. + * Must be called with mdev->graph_mutex held. + */ +int imx_media_find_mipi_csi2_channel(struct imx_media_dev *imxmd, + struct media_entity *start_entity) +{ + struct media_pad *pad; + int ret = -EPIPE; + + pad = find_pipeline_pad(imxmd, start_entity, IMX_MEDIA_GRP_ID_CSI2, + true); + if (pad) { + ret = pad->index - 1; + dev_dbg(imxmd->md.dev, "found vc%d from %s\n", + ret, start_entity->name); + } + + return ret; +} +EXPORT_SYMBOL_GPL(imx_media_find_mipi_csi2_channel); + +/* + * Find a subdev reached upstream from the given start entity in + * the current pipeline. + * Must be called with mdev->graph_mutex held. + */ +struct imx_media_subdev * +imx_media_find_upstream_subdev(struct imx_media_dev *imxmd, + struct media_entity *start_entity, + u32 grp_id) +{ + struct v4l2_subdev *sd; + + sd = find_upstream_subdev(imxmd, start_entity, grp_id); + if (!sd) + return ERR_PTR(-ENODEV); + + return imx_media_find_subdev_by_sd(imxmd, sd); +} +EXPORT_SYMBOL_GPL(imx_media_find_upstream_subdev); + +struct imx_media_subdev * +__imx_media_find_sensor(struct imx_media_dev *imxmd, + struct media_entity *start_entity) +{ + return imx_media_find_upstream_subdev(imxmd, start_entity, + IMX_MEDIA_GRP_ID_SENSOR); +} +EXPORT_SYMBOL_GPL(__imx_media_find_sensor); + +struct imx_media_subdev * +imx_media_find_sensor(struct imx_media_dev *imxmd, + struct media_entity *start_entity) +{ + struct imx_media_subdev *sensor; + + mutex_lock(&imxmd->md.graph_mutex); + sensor = __imx_media_find_sensor(imxmd, start_entity); + mutex_unlock(&imxmd->md.graph_mutex); + + return sensor; +} +EXPORT_SYMBOL_GPL(imx_media_find_sensor); + +/* + * Turn current pipeline streaming on/off starting from entity. + */ +int imx_media_pipeline_set_stream(struct imx_media_dev *imxmd, + struct media_entity *entity, + bool on) +{ + struct v4l2_subdev *sd; + int ret = 0; + + if (!is_media_entity_v4l2_subdev(entity)) + return -EINVAL; + sd = media_entity_to_v4l2_subdev(entity); + + mutex_lock(&imxmd->md.graph_mutex); + + if (on) { + ret = __media_pipeline_start(entity, &imxmd->pipe); + if (ret) + goto out; + ret = v4l2_subdev_call(sd, video, s_stream, 1); + if (ret) + __media_pipeline_stop(entity); + } else { + v4l2_subdev_call(sd, video, s_stream, 0); + if (entity->pipe) + __media_pipeline_stop(entity); + } + +out: + mutex_unlock(&imxmd->md.graph_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(imx_media_pipeline_set_stream); + +MODULE_DESCRIPTION("i.MX5/6 v4l2 media controller driver"); +MODULE_AUTHOR("Steve Longerbeam <steve_longerbeam@mentor.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/media/imx/imx-media-vdic.c b/drivers/staging/media/imx/imx-media-vdic.c new file mode 100644 index 000000000000..7eabdc4aa79f --- /dev/null +++ b/drivers/staging/media/imx/imx-media-vdic.c @@ -0,0 +1,1009 @@ +/* + * V4L2 Deinterlacer Subdev for Freescale i.MX5/6 SOC + * + * Copyright (c) 2017 Mentor Graphics Inc. + * + * 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; either version 2 of the License, or + * (at your option) any later version. + */ +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/timer.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-mc.h> +#include <media/v4l2-subdev.h> +#include <media/imx.h> +#include "imx-media.h" + +/* + * This subdev implements two different video pipelines: + * + * CSI -> VDIC + * + * In this pipeline, the CSI sends a single interlaced field F(n-1) + * directly to the VDIC (and optionally the following field F(n) + * can be sent to memory via IDMAC channel 13). This pipeline only works + * in VDIC's high motion mode, which only requires a single field for + * processing. The other motion modes (low and medium) require three + * fields, so this pipeline does not work in those modes. Also, it is + * not clear how this pipeline can deal with the various field orders + * (sequential BT/TB, interlaced BT/TB). + * + * MEM -> CH8,9,10 -> VDIC + * + * In this pipeline, previous field F(n-1), current field F(n), and next + * field F(n+1) are transferred to the VDIC via IDMAC channels 8,9,10. + * These memory buffers can come from a video output or mem2mem device. + * All motion modes are supported by this pipeline. + * + * The "direct" CSI->VDIC pipeline requires no DMA, but it can only be + * used in high motion mode. + */ + +struct vdic_priv; + +struct vdic_pipeline_ops { + int (*setup)(struct vdic_priv *priv); + void (*start)(struct vdic_priv *priv); + void (*stop)(struct vdic_priv *priv); + void (*disable)(struct vdic_priv *priv); +}; + +/* + * Min/Max supported width and heights. + */ +#define MIN_W 176 +#define MIN_H 144 +#define MAX_W_VDIC 968 +#define MAX_H_VDIC 2048 +#define W_ALIGN 4 /* multiple of 16 pixels */ +#define H_ALIGN 1 /* multiple of 2 lines */ +#define S_ALIGN 1 /* multiple of 2 */ + +struct vdic_priv { + struct device *dev; + struct ipu_soc *ipu; + struct imx_media_dev *md; + struct v4l2_subdev sd; + struct media_pad pad[VDIC_NUM_PADS]; + int ipu_id; + + /* lock to protect all members below */ + struct mutex lock; + + /* IPU units we require */ + struct ipu_vdi *vdi; + + int active_input_pad; + + struct ipuv3_channel *vdi_in_ch_p; /* F(n-1) transfer channel */ + struct ipuv3_channel *vdi_in_ch; /* F(n) transfer channel */ + struct ipuv3_channel *vdi_in_ch_n; /* F(n+1) transfer channel */ + + /* pipeline operations */ + struct vdic_pipeline_ops *ops; + + /* current and previous input buffers indirect path */ + struct imx_media_buffer *curr_in_buf; + struct imx_media_buffer *prev_in_buf; + + /* + * translated field type, input line stride, and field size + * for indirect path + */ + u32 fieldtype; + u32 in_stride; + u32 field_size; + + /* the source (a video device or subdev) */ + struct media_entity *src; + /* the sink that will receive the progressive out buffers */ + struct v4l2_subdev *sink_sd; + + struct v4l2_mbus_framefmt format_mbus[VDIC_NUM_PADS]; + const struct imx_media_pixfmt *cc[VDIC_NUM_PADS]; + struct v4l2_fract frame_interval[VDIC_NUM_PADS]; + + /* the video device at IDMAC input pad */ + struct imx_media_video_dev *vdev; + + bool csi_direct; /* using direct CSI->VDIC->IC pipeline */ + + /* motion select control */ + struct v4l2_ctrl_handler ctrl_hdlr; + enum ipu_motion_sel motion; + + int stream_count; +}; + +static void vdic_put_ipu_resources(struct vdic_priv *priv) +{ + if (!IS_ERR_OR_NULL(priv->vdi_in_ch_p)) + ipu_idmac_put(priv->vdi_in_ch_p); + priv->vdi_in_ch_p = NULL; + + if (!IS_ERR_OR_NULL(priv->vdi_in_ch)) + ipu_idmac_put(priv->vdi_in_ch); + priv->vdi_in_ch = NULL; + + if (!IS_ERR_OR_NULL(priv->vdi_in_ch_n)) + ipu_idmac_put(priv->vdi_in_ch_n); + priv->vdi_in_ch_n = NULL; + + if (!IS_ERR_OR_NULL(priv->vdi)) + ipu_vdi_put(priv->vdi); + priv->vdi = NULL; +} + +static int vdic_get_ipu_resources(struct vdic_priv *priv) +{ + int ret, err_chan; + + priv->ipu = priv->md->ipu[priv->ipu_id]; + + priv->vdi = ipu_vdi_get(priv->ipu); + if (IS_ERR(priv->vdi)) { + v4l2_err(&priv->sd, "failed to get VDIC\n"); + ret = PTR_ERR(priv->vdi); + goto out; + } + + if (!priv->csi_direct) { + priv->vdi_in_ch_p = ipu_idmac_get(priv->ipu, + IPUV3_CHANNEL_MEM_VDI_PREV); + if (IS_ERR(priv->vdi_in_ch_p)) { + err_chan = IPUV3_CHANNEL_MEM_VDI_PREV; + ret = PTR_ERR(priv->vdi_in_ch_p); + goto out_err_chan; + } + + priv->vdi_in_ch = ipu_idmac_get(priv->ipu, + IPUV3_CHANNEL_MEM_VDI_CUR); + if (IS_ERR(priv->vdi_in_ch)) { + err_chan = IPUV3_CHANNEL_MEM_VDI_CUR; + ret = PTR_ERR(priv->vdi_in_ch); + goto out_err_chan; + } + + priv->vdi_in_ch_n = ipu_idmac_get(priv->ipu, + IPUV3_CHANNEL_MEM_VDI_NEXT); + if (IS_ERR(priv->vdi_in_ch_n)) { + err_chan = IPUV3_CHANNEL_MEM_VDI_NEXT; + ret = PTR_ERR(priv->vdi_in_ch_n); + goto out_err_chan; + } + } + + return 0; + +out_err_chan: + v4l2_err(&priv->sd, "could not get IDMAC channel %u\n", err_chan); +out: + vdic_put_ipu_resources(priv); + return ret; +} + +/* + * This function is currently unused, but will be called when the + * output/mem2mem device at the IDMAC input pad sends us a new + * buffer. It kicks off the IDMAC read channels to bring in the + * buffer fields from memory and begin the conversions. + */ +static void __maybe_unused prepare_vdi_in_buffers(struct vdic_priv *priv, + struct imx_media_buffer *curr) +{ + dma_addr_t prev_phys, curr_phys, next_phys; + struct imx_media_buffer *prev; + struct vb2_buffer *curr_vb, *prev_vb; + u32 fs = priv->field_size; + u32 is = priv->in_stride; + + /* current input buffer is now previous */ + priv->prev_in_buf = priv->curr_in_buf; + priv->curr_in_buf = curr; + prev = priv->prev_in_buf ? priv->prev_in_buf : curr; + + prev_vb = &prev->vbuf.vb2_buf; + curr_vb = &curr->vbuf.vb2_buf; + + switch (priv->fieldtype) { + case V4L2_FIELD_SEQ_TB: + prev_phys = vb2_dma_contig_plane_dma_addr(prev_vb, 0); + curr_phys = vb2_dma_contig_plane_dma_addr(curr_vb, 0) + fs; + next_phys = vb2_dma_contig_plane_dma_addr(curr_vb, 0); + break; + case V4L2_FIELD_SEQ_BT: + prev_phys = vb2_dma_contig_plane_dma_addr(prev_vb, 0) + fs; + curr_phys = vb2_dma_contig_plane_dma_addr(curr_vb, 0); + next_phys = vb2_dma_contig_plane_dma_addr(curr_vb, 0) + fs; + break; + case V4L2_FIELD_INTERLACED_BT: + prev_phys = vb2_dma_contig_plane_dma_addr(prev_vb, 0) + is; + curr_phys = vb2_dma_contig_plane_dma_addr(curr_vb, 0); + next_phys = vb2_dma_contig_plane_dma_addr(curr_vb, 0) + is; + break; + default: + /* assume V4L2_FIELD_INTERLACED_TB */ + prev_phys = vb2_dma_contig_plane_dma_addr(prev_vb, 0); + curr_phys = vb2_dma_contig_plane_dma_addr(curr_vb, 0) + is; + next_phys = vb2_dma_contig_plane_dma_addr(curr_vb, 0); + break; + } + + ipu_cpmem_set_buffer(priv->vdi_in_ch_p, 0, prev_phys); + ipu_cpmem_set_buffer(priv->vdi_in_ch, 0, curr_phys); + ipu_cpmem_set_buffer(priv->vdi_in_ch_n, 0, next_phys); + + ipu_idmac_select_buffer(priv->vdi_in_ch_p, 0); + ipu_idmac_select_buffer(priv->vdi_in_ch, 0); + ipu_idmac_select_buffer(priv->vdi_in_ch_n, 0); +} + +static int setup_vdi_channel(struct vdic_priv *priv, + struct ipuv3_channel *channel, + dma_addr_t phys0, dma_addr_t phys1) +{ + struct imx_media_video_dev *vdev = priv->vdev; + unsigned int burst_size; + struct ipu_image image; + int ret; + + ipu_cpmem_zero(channel); + + memset(&image, 0, sizeof(image)); + image.pix = vdev->fmt.fmt.pix; + /* one field to VDIC channels */ + image.pix.height /= 2; + image.rect.width = image.pix.width; + image.rect.height = image.pix.height; + image.phys0 = phys0; + image.phys1 = phys1; + + ret = ipu_cpmem_set_image(channel, &image); + if (ret) + return ret; + + burst_size = (image.pix.width & 0xf) ? 8 : 16; + ipu_cpmem_set_burstsize(channel, burst_size); + + ipu_cpmem_set_axi_id(channel, 1); + + ipu_idmac_set_double_buffer(channel, false); + + return 0; +} + +static int vdic_setup_direct(struct vdic_priv *priv) +{ + /* set VDIC to receive from CSI for direct path */ + ipu_fsu_link(priv->ipu, IPUV3_CHANNEL_CSI_DIRECT, + IPUV3_CHANNEL_CSI_VDI_PREV); + + return 0; +} + +static void vdic_start_direct(struct vdic_priv *priv) +{ +} + +static void vdic_stop_direct(struct vdic_priv *priv) +{ +} + +static void vdic_disable_direct(struct vdic_priv *priv) +{ + ipu_fsu_unlink(priv->ipu, IPUV3_CHANNEL_CSI_DIRECT, + IPUV3_CHANNEL_CSI_VDI_PREV); +} + +static int vdic_setup_indirect(struct vdic_priv *priv) +{ + struct v4l2_mbus_framefmt *infmt; + const struct imx_media_pixfmt *incc; + int in_size, ret; + + infmt = &priv->format_mbus[VDIC_SINK_PAD_IDMAC]; + incc = priv->cc[VDIC_SINK_PAD_IDMAC]; + + in_size = (infmt->width * incc->bpp * infmt->height) >> 3; + + /* 1/2 full image size */ + priv->field_size = in_size / 2; + priv->in_stride = incc->planar ? + infmt->width : (infmt->width * incc->bpp) >> 3; + + priv->prev_in_buf = NULL; + priv->curr_in_buf = NULL; + + priv->fieldtype = infmt->field; + + /* init the vdi-in channels */ + ret = setup_vdi_channel(priv, priv->vdi_in_ch_p, 0, 0); + if (ret) + return ret; + ret = setup_vdi_channel(priv, priv->vdi_in_ch, 0, 0); + if (ret) + return ret; + return setup_vdi_channel(priv, priv->vdi_in_ch_n, 0, 0); +} + +static void vdic_start_indirect(struct vdic_priv *priv) +{ + /* enable the channels */ + ipu_idmac_enable_channel(priv->vdi_in_ch_p); + ipu_idmac_enable_channel(priv->vdi_in_ch); + ipu_idmac_enable_channel(priv->vdi_in_ch_n); +} + +static void vdic_stop_indirect(struct vdic_priv *priv) +{ + /* disable channels */ + ipu_idmac_disable_channel(priv->vdi_in_ch_p); + ipu_idmac_disable_channel(priv->vdi_in_ch); + ipu_idmac_disable_channel(priv->vdi_in_ch_n); +} + +static void vdic_disable_indirect(struct vdic_priv *priv) +{ +} + +static struct vdic_pipeline_ops direct_ops = { + .setup = vdic_setup_direct, + .start = vdic_start_direct, + .stop = vdic_stop_direct, + .disable = vdic_disable_direct, +}; + +static struct vdic_pipeline_ops indirect_ops = { + .setup = vdic_setup_indirect, + .start = vdic_start_indirect, + .stop = vdic_stop_indirect, + .disable = vdic_disable_indirect, +}; + +static int vdic_start(struct vdic_priv *priv) +{ + struct v4l2_mbus_framefmt *infmt; + int ret; + + infmt = &priv->format_mbus[priv->active_input_pad]; + + priv->ops = priv->csi_direct ? &direct_ops : &indirect_ops; + + ret = vdic_get_ipu_resources(priv); + if (ret) + return ret; + + /* + * init the VDIC. + * + * note we don't give infmt->code to ipu_vdi_setup(). The VDIC + * only supports 4:2:2 or 4:2:0, and this subdev will only + * negotiate 4:2:2 at its sink pads. + */ + ipu_vdi_setup(priv->vdi, MEDIA_BUS_FMT_UYVY8_2X8, + infmt->width, infmt->height); + ipu_vdi_set_field_order(priv->vdi, V4L2_STD_UNKNOWN, infmt->field); + ipu_vdi_set_motion(priv->vdi, priv->motion); + + ret = priv->ops->setup(priv); + if (ret) + goto out_put_ipu; + + ipu_vdi_enable(priv->vdi); + + priv->ops->start(priv); + + return 0; + +out_put_ipu: + vdic_put_ipu_resources(priv); + return ret; +} + +static void vdic_stop(struct vdic_priv *priv) +{ + priv->ops->stop(priv); + ipu_vdi_disable(priv->vdi); + priv->ops->disable(priv); + + vdic_put_ipu_resources(priv); +} + +/* + * V4L2 subdev operations. + */ + +static int vdic_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct vdic_priv *priv = container_of(ctrl->handler, + struct vdic_priv, ctrl_hdlr); + enum ipu_motion_sel motion; + int ret = 0; + + mutex_lock(&priv->lock); + + switch (ctrl->id) { + case V4L2_CID_DEINTERLACING_MODE: + motion = ctrl->val; + if (motion != priv->motion) { + /* can't change motion control mid-streaming */ + if (priv->stream_count > 0) { + ret = -EBUSY; + goto out; + } + priv->motion = motion; + } + break; + default: + v4l2_err(&priv->sd, "Invalid control\n"); + ret = -EINVAL; + } + +out: + mutex_unlock(&priv->lock); + return ret; +} + +static const struct v4l2_ctrl_ops vdic_ctrl_ops = { + .s_ctrl = vdic_s_ctrl, +}; + +static const char * const vdic_ctrl_motion_menu[] = { + "No Motion Compensation", + "Low Motion", + "Medium Motion", + "High Motion", +}; + +static int vdic_init_controls(struct vdic_priv *priv) +{ + struct v4l2_ctrl_handler *hdlr = &priv->ctrl_hdlr; + int ret; + + v4l2_ctrl_handler_init(hdlr, 1); + + v4l2_ctrl_new_std_menu_items(hdlr, &vdic_ctrl_ops, + V4L2_CID_DEINTERLACING_MODE, + HIGH_MOTION, 0, HIGH_MOTION, + vdic_ctrl_motion_menu); + + priv->sd.ctrl_handler = hdlr; + + if (hdlr->error) { + ret = hdlr->error; + goto out_free; + } + + v4l2_ctrl_handler_setup(hdlr); + return 0; + +out_free: + v4l2_ctrl_handler_free(hdlr); + return ret; +} + +static int vdic_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct vdic_priv *priv = v4l2_get_subdevdata(sd); + struct v4l2_subdev *src_sd = NULL; + int ret = 0; + + mutex_lock(&priv->lock); + + if (!priv->src || !priv->sink_sd) { + ret = -EPIPE; + goto out; + } + + if (priv->csi_direct) + src_sd = media_entity_to_v4l2_subdev(priv->src); + + /* + * enable/disable streaming only if stream_count is + * going from 0 to 1 / 1 to 0. + */ + if (priv->stream_count != !enable) + goto update_count; + + dev_dbg(priv->dev, "stream %s\n", enable ? "ON" : "OFF"); + + if (enable) + ret = vdic_start(priv); + else + vdic_stop(priv); + if (ret) + goto out; + + if (src_sd) { + /* start/stop upstream */ + ret = v4l2_subdev_call(src_sd, video, s_stream, enable); + ret = (ret && ret != -ENOIOCTLCMD) ? ret : 0; + if (ret) { + if (enable) + vdic_stop(priv); + goto out; + } + } + +update_count: + priv->stream_count += enable ? 1 : -1; + if (priv->stream_count < 0) + priv->stream_count = 0; +out: + mutex_unlock(&priv->lock); + return ret; +} + +static struct v4l2_mbus_framefmt * +__vdic_get_fmt(struct vdic_priv *priv, struct v4l2_subdev_pad_config *cfg, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(&priv->sd, cfg, pad); + else + return &priv->format_mbus[pad]; +} + +static int vdic_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad >= VDIC_NUM_PADS) + return -EINVAL; + + return imx_media_enum_ipu_format(&code->code, code->index, CS_SEL_YUV); +} + +static int vdic_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *sdformat) +{ + struct vdic_priv *priv = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *fmt; + int ret = 0; + + if (sdformat->pad >= VDIC_NUM_PADS) + return -EINVAL; + + mutex_lock(&priv->lock); + + fmt = __vdic_get_fmt(priv, cfg, sdformat->pad, sdformat->which); + if (!fmt) { + ret = -EINVAL; + goto out; + } + + sdformat->format = *fmt; +out: + mutex_unlock(&priv->lock); + return ret; +} + +static void vdic_try_fmt(struct vdic_priv *priv, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *sdformat, + const struct imx_media_pixfmt **cc) +{ + struct v4l2_mbus_framefmt *infmt; + + *cc = imx_media_find_ipu_format(sdformat->format.code, CS_SEL_YUV); + if (!*cc) { + u32 code; + + imx_media_enum_ipu_format(&code, 0, CS_SEL_YUV); + *cc = imx_media_find_ipu_format(code, CS_SEL_YUV); + sdformat->format.code = (*cc)->codes[0]; + } + + infmt = __vdic_get_fmt(priv, cfg, priv->active_input_pad, + sdformat->which); + + switch (sdformat->pad) { + case VDIC_SRC_PAD_DIRECT: + sdformat->format = *infmt; + /* output is always progressive! */ + sdformat->format.field = V4L2_FIELD_NONE; + break; + case VDIC_SINK_PAD_DIRECT: + case VDIC_SINK_PAD_IDMAC: + v4l_bound_align_image(&sdformat->format.width, + MIN_W, MAX_W_VDIC, W_ALIGN, + &sdformat->format.height, + MIN_H, MAX_H_VDIC, H_ALIGN, S_ALIGN); + + imx_media_fill_default_mbus_fields(&sdformat->format, infmt, + true); + + /* input must be interlaced! Choose SEQ_TB if not */ + if (!V4L2_FIELD_HAS_BOTH(sdformat->format.field)) + sdformat->format.field = V4L2_FIELD_SEQ_TB; + break; + } +} + +static int vdic_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *sdformat) +{ + struct vdic_priv *priv = v4l2_get_subdevdata(sd); + const struct imx_media_pixfmt *cc; + struct v4l2_mbus_framefmt *fmt; + int ret = 0; + + if (sdformat->pad >= VDIC_NUM_PADS) + return -EINVAL; + + mutex_lock(&priv->lock); + + if (priv->stream_count > 0) { + ret = -EBUSY; + goto out; + } + + vdic_try_fmt(priv, cfg, sdformat, &cc); + + fmt = __vdic_get_fmt(priv, cfg, sdformat->pad, sdformat->which); + *fmt = sdformat->format; + + /* propagate format to source pad */ + if (sdformat->pad == VDIC_SINK_PAD_DIRECT || + sdformat->pad == VDIC_SINK_PAD_IDMAC) { + const struct imx_media_pixfmt *outcc; + struct v4l2_mbus_framefmt *outfmt; + struct v4l2_subdev_format format; + + format.pad = VDIC_SRC_PAD_DIRECT; + format.which = sdformat->which; + format.format = sdformat->format; + vdic_try_fmt(priv, cfg, &format, &outcc); + + outfmt = __vdic_get_fmt(priv, cfg, VDIC_SRC_PAD_DIRECT, + sdformat->which); + *outfmt = format.format; + if (sdformat->which == V4L2_SUBDEV_FORMAT_ACTIVE) + priv->cc[VDIC_SRC_PAD_DIRECT] = outcc; + } + + if (sdformat->which == V4L2_SUBDEV_FORMAT_ACTIVE) + priv->cc[sdformat->pad] = cc; +out: + mutex_unlock(&priv->lock); + return ret; +} + +static int vdic_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); + struct vdic_priv *priv = v4l2_get_subdevdata(sd); + struct v4l2_subdev *remote_sd; + int ret = 0; + + dev_dbg(priv->dev, "link setup %s -> %s", remote->entity->name, + local->entity->name); + + mutex_lock(&priv->lock); + + if (local->flags & MEDIA_PAD_FL_SOURCE) { + if (!is_media_entity_v4l2_subdev(remote->entity)) { + ret = -EINVAL; + goto out; + } + + remote_sd = media_entity_to_v4l2_subdev(remote->entity); + + if (flags & MEDIA_LNK_FL_ENABLED) { + if (priv->sink_sd) { + ret = -EBUSY; + goto out; + } + priv->sink_sd = remote_sd; + } else { + priv->sink_sd = NULL; + } + + goto out; + } + + /* this is a sink pad */ + + if (flags & MEDIA_LNK_FL_ENABLED) { + if (priv->src) { + ret = -EBUSY; + goto out; + } + } else { + priv->src = NULL; + goto out; + } + + if (local->index == VDIC_SINK_PAD_IDMAC) { + struct imx_media_video_dev *vdev = priv->vdev; + + if (!is_media_entity_v4l2_video_device(remote->entity)) { + ret = -EINVAL; + goto out; + } + if (!vdev) { + ret = -ENODEV; + goto out; + } + + priv->csi_direct = false; + } else { + if (!is_media_entity_v4l2_subdev(remote->entity)) { + ret = -EINVAL; + goto out; + } + + remote_sd = media_entity_to_v4l2_subdev(remote->entity); + + /* direct pad must connect to a CSI */ + if (!(remote_sd->grp_id & IMX_MEDIA_GRP_ID_CSI) || + remote->index != CSI_SRC_PAD_DIRECT) { + ret = -EINVAL; + goto out; + } + + priv->csi_direct = true; + } + + priv->src = remote->entity; + /* record which input pad is now active */ + priv->active_input_pad = local->index; +out: + mutex_unlock(&priv->lock); + return ret; +} + +static int vdic_link_validate(struct v4l2_subdev *sd, + struct media_link *link, + struct v4l2_subdev_format *source_fmt, + struct v4l2_subdev_format *sink_fmt) +{ + struct vdic_priv *priv = v4l2_get_subdevdata(sd); + int ret; + + ret = v4l2_subdev_link_validate_default(sd, link, + source_fmt, sink_fmt); + if (ret) + return ret; + + mutex_lock(&priv->lock); + + if (priv->csi_direct && priv->motion != HIGH_MOTION) { + v4l2_err(&priv->sd, + "direct CSI pipeline requires high motion\n"); + ret = -EINVAL; + } + + mutex_unlock(&priv->lock); + return ret; +} + +static int vdic_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct vdic_priv *priv = v4l2_get_subdevdata(sd); + + if (fi->pad >= VDIC_NUM_PADS) + return -EINVAL; + + mutex_lock(&priv->lock); + + fi->interval = priv->frame_interval[fi->pad]; + + mutex_unlock(&priv->lock); + + return 0; +} + +static int vdic_s_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct vdic_priv *priv = v4l2_get_subdevdata(sd); + struct v4l2_fract *input_fi, *output_fi; + int ret = 0; + + mutex_lock(&priv->lock); + + input_fi = &priv->frame_interval[priv->active_input_pad]; + output_fi = &priv->frame_interval[VDIC_SRC_PAD_DIRECT]; + + switch (fi->pad) { + case VDIC_SINK_PAD_DIRECT: + case VDIC_SINK_PAD_IDMAC: + /* No limits on input frame interval */ + /* Reset output interval */ + *output_fi = fi->interval; + if (priv->csi_direct) + output_fi->denominator *= 2; + break; + case VDIC_SRC_PAD_DIRECT: + /* + * frame rate at output pad is double input + * rate when using direct CSI->VDIC pipeline. + * + * TODO: implement VDIC frame skipping + */ + fi->interval = *input_fi; + if (priv->csi_direct) + fi->interval.denominator *= 2; + break; + default: + ret = -EINVAL; + goto out; + } + + priv->frame_interval[fi->pad] = fi->interval; +out: + mutex_unlock(&priv->lock); + return ret; +} + +/* + * retrieve our pads parsed from the OF graph by the media device + */ +static int vdic_registered(struct v4l2_subdev *sd) +{ + struct vdic_priv *priv = v4l2_get_subdevdata(sd); + int i, ret; + u32 code; + + /* get media device */ + priv->md = dev_get_drvdata(sd->v4l2_dev->dev); + + for (i = 0; i < VDIC_NUM_PADS; i++) { + priv->pad[i].flags = (i == VDIC_SRC_PAD_DIRECT) ? + MEDIA_PAD_FL_SOURCE : MEDIA_PAD_FL_SINK; + + code = 0; + if (i != VDIC_SINK_PAD_IDMAC) + imx_media_enum_ipu_format(&code, 0, CS_SEL_YUV); + + /* set a default mbus format */ + ret = imx_media_init_mbus_fmt(&priv->format_mbus[i], + 640, 480, code, V4L2_FIELD_NONE, + &priv->cc[i]); + if (ret) + return ret; + + /* init default frame interval */ + priv->frame_interval[i].numerator = 1; + priv->frame_interval[i].denominator = 30; + if (i == VDIC_SRC_PAD_DIRECT) + priv->frame_interval[i].denominator *= 2; + } + + priv->active_input_pad = VDIC_SINK_PAD_DIRECT; + + ret = vdic_init_controls(priv); + if (ret) + return ret; + + ret = media_entity_pads_init(&sd->entity, VDIC_NUM_PADS, priv->pad); + if (ret) + v4l2_ctrl_handler_free(&priv->ctrl_hdlr); + + return ret; +} + +static void vdic_unregistered(struct v4l2_subdev *sd) +{ + struct vdic_priv *priv = v4l2_get_subdevdata(sd); + + v4l2_ctrl_handler_free(&priv->ctrl_hdlr); +} + +static const struct v4l2_subdev_pad_ops vdic_pad_ops = { + .enum_mbus_code = vdic_enum_mbus_code, + .get_fmt = vdic_get_fmt, + .set_fmt = vdic_set_fmt, + .link_validate = vdic_link_validate, +}; + +static const struct v4l2_subdev_video_ops vdic_video_ops = { + .g_frame_interval = vdic_g_frame_interval, + .s_frame_interval = vdic_s_frame_interval, + .s_stream = vdic_s_stream, +}; + +static const struct media_entity_operations vdic_entity_ops = { + .link_setup = vdic_link_setup, + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct v4l2_subdev_ops vdic_subdev_ops = { + .video = &vdic_video_ops, + .pad = &vdic_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops vdic_internal_ops = { + .registered = vdic_registered, + .unregistered = vdic_unregistered, +}; + +static int imx_vdic_probe(struct platform_device *pdev) +{ + struct imx_media_internal_sd_platformdata *pdata; + struct vdic_priv *priv; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + platform_set_drvdata(pdev, &priv->sd); + priv->dev = &pdev->dev; + + pdata = priv->dev->platform_data; + priv->ipu_id = pdata->ipu_id; + + v4l2_subdev_init(&priv->sd, &vdic_subdev_ops); + v4l2_set_subdevdata(&priv->sd, priv); + priv->sd.internal_ops = &vdic_internal_ops; + priv->sd.entity.ops = &vdic_entity_ops; + priv->sd.entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; + priv->sd.dev = &pdev->dev; + priv->sd.owner = THIS_MODULE; + priv->sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE; + /* get our group id */ + priv->sd.grp_id = pdata->grp_id; + strncpy(priv->sd.name, pdata->sd_name, sizeof(priv->sd.name)); + + mutex_init(&priv->lock); + + ret = v4l2_async_register_subdev(&priv->sd); + if (ret) + goto free; + + return 0; +free: + mutex_destroy(&priv->lock); + return ret; +} + +static int imx_vdic_remove(struct platform_device *pdev) +{ + struct v4l2_subdev *sd = platform_get_drvdata(pdev); + struct vdic_priv *priv = v4l2_get_subdevdata(sd); + + v4l2_info(sd, "Removing\n"); + + v4l2_async_unregister_subdev(sd); + mutex_destroy(&priv->lock); + media_entity_cleanup(&sd->entity); + + return 0; +} + +static const struct platform_device_id imx_vdic_ids[] = { + { .name = "imx-ipuv3-vdic" }, + { }, +}; +MODULE_DEVICE_TABLE(platform, imx_vdic_ids); + +static struct platform_driver imx_vdic_driver = { + .probe = imx_vdic_probe, + .remove = imx_vdic_remove, + .id_table = imx_vdic_ids, + .driver = { + .name = "imx-ipuv3-vdic", + }, +}; +module_platform_driver(imx_vdic_driver); + +MODULE_DESCRIPTION("i.MX VDIC subdev driver"); +MODULE_AUTHOR("Steve Longerbeam <steve_longerbeam@mentor.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:imx-ipuv3-vdic"); diff --git a/drivers/staging/media/imx/imx-media.h b/drivers/staging/media/imx/imx-media.h new file mode 100644 index 000000000000..d409170632bd --- /dev/null +++ b/drivers/staging/media/imx/imx-media.h @@ -0,0 +1,325 @@ +/* + * V4L2 Media Controller Driver for Freescale i.MX5/6 SOC + * + * Copyright (c) 2016 Mentor Graphics Inc. + * + * 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; either version 2 of the License, or + * (at your option) any later version. + */ +#ifndef _IMX_MEDIA_H +#define _IMX_MEDIA_H + +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-subdev.h> +#include <media/videobuf2-dma-contig.h> +#include <video/imx-ipu-v3.h> + +/* + * This is somewhat arbitrary, but we need at least: + * - 4 video devices per IPU + * - 3 IC subdevs per IPU + * - 1 VDIC subdev per IPU + * - 2 CSI subdevs per IPU + * - 1 mipi-csi2 receiver subdev + * - 2 video-mux subdevs + * - 2 camera sensor subdevs per IPU (1 parallel, 1 mipi-csi2) + * + */ +/* max video devices */ +#define IMX_MEDIA_MAX_VDEVS 8 +/* max subdevices */ +#define IMX_MEDIA_MAX_SUBDEVS 32 +/* max pads per subdev */ +#define IMX_MEDIA_MAX_PADS 16 +/* max links per pad */ +#define IMX_MEDIA_MAX_LINKS 8 + +/* + * Pad definitions for the subdevs with multiple source or + * sink pads + */ + +/* ipu_csi */ +enum { + CSI_SINK_PAD = 0, + CSI_SRC_PAD_DIRECT, + CSI_SRC_PAD_IDMAC, + CSI_NUM_PADS, +}; + +#define CSI_NUM_SINK_PADS 1 +#define CSI_NUM_SRC_PADS 2 + +/* ipu_vdic */ +enum { + VDIC_SINK_PAD_DIRECT = 0, + VDIC_SINK_PAD_IDMAC, + VDIC_SRC_PAD_DIRECT, + VDIC_NUM_PADS, +}; + +#define VDIC_NUM_SINK_PADS 2 +#define VDIC_NUM_SRC_PADS 1 + +/* ipu_ic_prp */ +enum { + PRP_SINK_PAD = 0, + PRP_SRC_PAD_PRPENC, + PRP_SRC_PAD_PRPVF, + PRP_NUM_PADS, +}; + +#define PRP_NUM_SINK_PADS 1 +#define PRP_NUM_SRC_PADS 2 + +/* ipu_ic_prpencvf */ +enum { + PRPENCVF_SINK_PAD = 0, + PRPENCVF_SRC_PAD, + PRPENCVF_NUM_PADS, +}; + +#define PRPENCVF_NUM_SINK_PADS 1 +#define PRPENCVF_NUM_SRC_PADS 1 + +/* How long to wait for EOF interrupts in the buffer-capture subdevs */ +#define IMX_MEDIA_EOF_TIMEOUT 1000 + +struct imx_media_pixfmt { + u32 fourcc; + u32 codes[4]; + int bpp; /* total bpp */ + enum ipu_color_space cs; + bool planar; /* is a planar format */ + bool bayer; /* is a raw bayer format */ + bool ipufmt; /* is one of the IPU internal formats */ +}; + +struct imx_media_buffer { + struct vb2_v4l2_buffer vbuf; /* v4l buffer must be first */ + struct list_head list; +}; + +struct imx_media_video_dev { + struct video_device *vfd; + + /* the user format */ + struct v4l2_format fmt; + const struct imx_media_pixfmt *cc; +}; + +static inline struct imx_media_buffer *to_imx_media_vb(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + + return container_of(vbuf, struct imx_media_buffer, vbuf); +} + +struct imx_media_link { + struct device_node *remote_sd_node; + char remote_devname[32]; + int local_pad; + int remote_pad; +}; + +struct imx_media_pad { + struct media_pad pad; + struct imx_media_link link[IMX_MEDIA_MAX_LINKS]; + bool devnode; /* does this pad link to a device node */ + int num_links; + + /* + * list of video devices that can be reached from this pad, + * list is only valid for source pads. + */ + struct imx_media_video_dev *vdev[IMX_MEDIA_MAX_VDEVS]; + int num_vdevs; +}; + +struct imx_media_internal_sd_platformdata { + char sd_name[V4L2_SUBDEV_NAME_SIZE]; + u32 grp_id; + int ipu_id; +}; + +struct imx_media_subdev { + struct v4l2_async_subdev asd; + struct v4l2_subdev *sd; /* set when bound */ + + struct imx_media_pad pad[IMX_MEDIA_MAX_PADS]; + int num_sink_pads; + int num_src_pads; + + /* the platform device if this is an internal subdev */ + struct platform_device *pdev; + /* the devname is needed for async devname match */ + char devname[32]; + + /* if this is a sensor */ + struct v4l2_fwnode_endpoint sensor_ep; +}; + +struct imx_media_dev { + struct media_device md; + struct v4l2_device v4l2_dev; + + /* the pipeline object */ + struct media_pipeline pipe; + + struct mutex mutex; /* protect elements below */ + + /* master subdevice list */ + struct imx_media_subdev subdev[IMX_MEDIA_MAX_SUBDEVS]; + int num_subdevs; + + /* master video device list */ + struct imx_media_video_dev *vdev[IMX_MEDIA_MAX_VDEVS]; + int num_vdevs; + + /* IPUs this media driver control, valid after subdevs bound */ + struct ipu_soc *ipu[2]; + + /* for async subdev registration */ + struct v4l2_async_subdev *async_ptrs[IMX_MEDIA_MAX_SUBDEVS]; + struct v4l2_async_notifier subdev_notifier; +}; + +enum codespace_sel { + CS_SEL_YUV = 0, + CS_SEL_RGB, + CS_SEL_ANY, +}; + +const struct imx_media_pixfmt * +imx_media_find_format(u32 fourcc, enum codespace_sel cs_sel, bool allow_bayer); +int imx_media_enum_format(u32 *fourcc, u32 index, enum codespace_sel cs_sel); +const struct imx_media_pixfmt * +imx_media_find_mbus_format(u32 code, enum codespace_sel cs_sel, + bool allow_bayer); +int imx_media_enum_mbus_format(u32 *code, u32 index, enum codespace_sel cs_sel, + bool allow_bayer); +const struct imx_media_pixfmt * +imx_media_find_ipu_format(u32 code, enum codespace_sel cs_sel); +int imx_media_enum_ipu_format(u32 *code, u32 index, enum codespace_sel cs_sel); + +int imx_media_init_mbus_fmt(struct v4l2_mbus_framefmt *mbus, + u32 width, u32 height, u32 code, u32 field, + const struct imx_media_pixfmt **cc); +void imx_media_fill_default_mbus_fields(struct v4l2_mbus_framefmt *tryfmt, + struct v4l2_mbus_framefmt *fmt, + bool ic_route); +int imx_media_mbus_fmt_to_pix_fmt(struct v4l2_pix_format *pix, + struct v4l2_mbus_framefmt *mbus, + const struct imx_media_pixfmt *cc); +int imx_media_mbus_fmt_to_ipu_image(struct ipu_image *image, + struct v4l2_mbus_framefmt *mbus); +int imx_media_ipu_image_to_mbus_fmt(struct v4l2_mbus_framefmt *mbus, + struct ipu_image *image); + +struct imx_media_subdev * +imx_media_find_async_subdev(struct imx_media_dev *imxmd, + struct device_node *np, + const char *devname); +struct imx_media_subdev * +imx_media_add_async_subdev(struct imx_media_dev *imxmd, + struct device_node *np, + struct platform_device *pdev); +int imx_media_add_pad_link(struct imx_media_dev *imxmd, + struct imx_media_pad *pad, + struct device_node *remote_node, + const char *remote_devname, + int local_pad, int remote_pad); + +void imx_media_grp_id_to_sd_name(char *sd_name, int sz, + u32 grp_id, int ipu_id); + +int imx_media_add_internal_subdevs(struct imx_media_dev *imxmd, + struct imx_media_subdev *csi[4]); +void imx_media_remove_internal_subdevs(struct imx_media_dev *imxmd); + +struct imx_media_subdev * +imx_media_find_subdev_by_sd(struct imx_media_dev *imxmd, + struct v4l2_subdev *sd); +struct imx_media_subdev * +imx_media_find_subdev_by_id(struct imx_media_dev *imxmd, + u32 grp_id); +int imx_media_add_video_device(struct imx_media_dev *imxmd, + struct imx_media_video_dev *vdev); +int imx_media_find_mipi_csi2_channel(struct imx_media_dev *imxmd, + struct media_entity *start_entity); +struct imx_media_subdev * +imx_media_find_upstream_subdev(struct imx_media_dev *imxmd, + struct media_entity *start_entity, + u32 grp_id); +struct imx_media_subdev * +__imx_media_find_sensor(struct imx_media_dev *imxmd, + struct media_entity *start_entity); +struct imx_media_subdev * +imx_media_find_sensor(struct imx_media_dev *imxmd, + struct media_entity *start_entity); + +struct imx_media_dma_buf { + void *virt; + dma_addr_t phys; + unsigned long len; +}; + +void imx_media_free_dma_buf(struct imx_media_dev *imxmd, + struct imx_media_dma_buf *buf); +int imx_media_alloc_dma_buf(struct imx_media_dev *imxmd, + struct imx_media_dma_buf *buf, + int size); + +int imx_media_pipeline_set_stream(struct imx_media_dev *imxmd, + struct media_entity *entity, + bool on); + +/* imx-media-fim.c */ +struct imx_media_fim; +void imx_media_fim_eof_monitor(struct imx_media_fim *fim, struct timespec *ts); +int imx_media_fim_set_stream(struct imx_media_fim *fim, + const struct v4l2_fract *frame_interval, + bool on); +int imx_media_fim_add_controls(struct imx_media_fim *fim); +struct imx_media_fim *imx_media_fim_init(struct v4l2_subdev *sd); +void imx_media_fim_free(struct imx_media_fim *fim); + +/* imx-media-of.c */ +struct imx_media_subdev * +imx_media_of_find_subdev(struct imx_media_dev *imxmd, + struct device_node *np, + const char *name); +int imx_media_of_parse(struct imx_media_dev *dev, + struct imx_media_subdev *(*csi)[4], + struct device_node *np); + +/* imx-media-capture.c */ +struct imx_media_video_dev * +imx_media_capture_device_init(struct v4l2_subdev *src_sd, int pad); +void imx_media_capture_device_remove(struct imx_media_video_dev *vdev); +int imx_media_capture_device_register(struct imx_media_video_dev *vdev); +void imx_media_capture_device_unregister(struct imx_media_video_dev *vdev); +struct imx_media_buffer * +imx_media_capture_device_next_buf(struct imx_media_video_dev *vdev); +void imx_media_capture_device_set_format(struct imx_media_video_dev *vdev, + struct v4l2_pix_format *pix); +void imx_media_capture_device_error(struct imx_media_video_dev *vdev); + +/* subdev group ids */ +#define IMX_MEDIA_GRP_ID_SENSOR (1 << 8) +#define IMX_MEDIA_GRP_ID_VIDMUX (1 << 9) +#define IMX_MEDIA_GRP_ID_CSI2 (1 << 10) +#define IMX_MEDIA_GRP_ID_CSI_BIT 11 +#define IMX_MEDIA_GRP_ID_CSI (0x3 << IMX_MEDIA_GRP_ID_CSI_BIT) +#define IMX_MEDIA_GRP_ID_CSI0 (1 << IMX_MEDIA_GRP_ID_CSI_BIT) +#define IMX_MEDIA_GRP_ID_CSI1 (2 << IMX_MEDIA_GRP_ID_CSI_BIT) +#define IMX_MEDIA_GRP_ID_VDIC (1 << 13) +#define IMX_MEDIA_GRP_ID_IC_PRP (1 << 14) +#define IMX_MEDIA_GRP_ID_IC_PRPENC (1 << 15) +#define IMX_MEDIA_GRP_ID_IC_PRPVF (1 << 16) + +#endif diff --git a/drivers/staging/media/imx/imx6-mipi-csi2.c b/drivers/staging/media/imx/imx6-mipi-csi2.c new file mode 100644 index 000000000000..5061f3f524fd --- /dev/null +++ b/drivers/staging/media/imx/imx6-mipi-csi2.c @@ -0,0 +1,698 @@ +/* + * MIPI CSI-2 Receiver Subdev for Freescale i.MX6 SOC. + * + * Copyright (c) 2012-2017 Mentor Graphics Inc. + * + * 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; either version 2 of the License, or + * (at your option) any later version. + */ +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-subdev.h> +#include "imx-media.h" + +/* + * there must be 5 pads: 1 input pad from sensor, and + * the 4 virtual channel output pads + */ +#define CSI2_SINK_PAD 0 +#define CSI2_NUM_SINK_PADS 1 +#define CSI2_NUM_SRC_PADS 4 +#define CSI2_NUM_PADS 5 + +/* + * The default maximum bit-rate per lane in Mbps, if the + * source subdev does not provide V4L2_CID_LINK_FREQ. + */ +#define CSI2_DEFAULT_MAX_MBPS 849 + +struct csi2_dev { + struct device *dev; + struct v4l2_subdev sd; + struct media_pad pad[CSI2_NUM_PADS]; + struct clk *dphy_clk; + struct clk *pllref_clk; + struct clk *pix_clk; /* what is this? */ + void __iomem *base; + struct v4l2_fwnode_bus_mipi_csi2 bus; + + /* lock to protect all members below */ + struct mutex lock; + + struct v4l2_mbus_framefmt format_mbus; + + int stream_count; + struct v4l2_subdev *src_sd; + bool sink_linked[CSI2_NUM_SRC_PADS]; +}; + +#define DEVICE_NAME "imx6-mipi-csi2" + +/* Register offsets */ +#define CSI2_VERSION 0x000 +#define CSI2_N_LANES 0x004 +#define CSI2_PHY_SHUTDOWNZ 0x008 +#define CSI2_DPHY_RSTZ 0x00c +#define CSI2_RESETN 0x010 +#define CSI2_PHY_STATE 0x014 +#define PHY_STOPSTATEDATA_BIT 4 +#define PHY_STOPSTATEDATA(n) BIT(PHY_STOPSTATEDATA_BIT + (n)) +#define PHY_RXCLKACTIVEHS BIT(8) +#define PHY_RXULPSCLKNOT BIT(9) +#define PHY_STOPSTATECLK BIT(10) +#define CSI2_DATA_IDS_1 0x018 +#define CSI2_DATA_IDS_2 0x01c +#define CSI2_ERR1 0x020 +#define CSI2_ERR2 0x024 +#define CSI2_MSK1 0x028 +#define CSI2_MSK2 0x02c +#define CSI2_PHY_TST_CTRL0 0x030 +#define PHY_TESTCLR BIT(0) +#define PHY_TESTCLK BIT(1) +#define CSI2_PHY_TST_CTRL1 0x034 +#define PHY_TESTEN BIT(16) +/* + * i.MX CSI2IPU Gasket registers follow. The CSI2IPU gasket is + * not part of the MIPI CSI-2 core, but its registers fall in the + * same register map range. + */ +#define CSI2IPU_GASKET 0xf00 +#define CSI2IPU_YUV422_YUYV BIT(2) + +static inline struct csi2_dev *sd_to_dev(struct v4l2_subdev *sdev) +{ + return container_of(sdev, struct csi2_dev, sd); +} + +/* + * The required sequence of MIPI CSI-2 startup as specified in the i.MX6 + * reference manual is as follows: + * + * 1. Deassert presetn signal (global reset). + * It's not clear what this "global reset" signal is (maybe APB + * global reset), but in any case this step would be probably + * be carried out during driver load in csi2_probe(). + * + * 2. Configure MIPI Camera Sensor to put all Tx lanes in LP-11 state. + * This must be carried out by the MIPI sensor's s_power(ON) subdev + * op. + * + * 3. D-PHY initialization. + * 4. CSI2 Controller programming (Set N_LANES, deassert PHY_SHUTDOWNZ, + * deassert PHY_RSTZ, deassert CSI2_RESETN). + * 5. Read the PHY status register (PHY_STATE) to confirm that all data and + * clock lanes of the D-PHY are in LP-11 state. + * 6. Configure the MIPI Camera Sensor to start transmitting a clock on the + * D-PHY clock lane. + * 7. CSI2 Controller programming - Read the PHY status register (PHY_STATE) + * to confirm that the D-PHY is receiving a clock on the D-PHY clock lane. + * + * All steps 3 through 7 are carried out by csi2_s_stream(ON) here. Step + * 6 is accomplished by calling the source subdev's s_stream(ON) between + * steps 5 and 7. + */ + +static void csi2_enable(struct csi2_dev *csi2, bool enable) +{ + if (enable) { + writel(0x1, csi2->base + CSI2_PHY_SHUTDOWNZ); + writel(0x1, csi2->base + CSI2_DPHY_RSTZ); + writel(0x1, csi2->base + CSI2_RESETN); + } else { + writel(0x0, csi2->base + CSI2_PHY_SHUTDOWNZ); + writel(0x0, csi2->base + CSI2_DPHY_RSTZ); + writel(0x0, csi2->base + CSI2_RESETN); + } +} + +static void csi2_set_lanes(struct csi2_dev *csi2) +{ + int lanes = csi2->bus.num_data_lanes; + + writel(lanes - 1, csi2->base + CSI2_N_LANES); +} + +static void dw_mipi_csi2_phy_write(struct csi2_dev *csi2, + u32 test_code, u32 test_data) +{ + /* Clear PHY test interface */ + writel(PHY_TESTCLR, csi2->base + CSI2_PHY_TST_CTRL0); + writel(0x0, csi2->base + CSI2_PHY_TST_CTRL1); + writel(0x0, csi2->base + CSI2_PHY_TST_CTRL0); + + /* Raise test interface strobe signal */ + writel(PHY_TESTCLK, csi2->base + CSI2_PHY_TST_CTRL0); + + /* Configure address write on falling edge and lower strobe signal */ + writel(PHY_TESTEN | test_code, csi2->base + CSI2_PHY_TST_CTRL1); + writel(0x0, csi2->base + CSI2_PHY_TST_CTRL0); + + /* Configure data write on rising edge and raise strobe signal */ + writel(test_data, csi2->base + CSI2_PHY_TST_CTRL1); + writel(PHY_TESTCLK, csi2->base + CSI2_PHY_TST_CTRL0); + + /* Clear strobe signal */ + writel(0x0, csi2->base + CSI2_PHY_TST_CTRL0); +} + +/* + * This table is based on the table documented at + * https://community.nxp.com/docs/DOC-94312. It assumes + * a 27MHz D-PHY pll reference clock. + */ +static const struct { + u32 max_mbps; + u32 hsfreqrange_sel; +} hsfreq_map[] = { + { 90, 0x00}, {100, 0x20}, {110, 0x40}, {125, 0x02}, + {140, 0x22}, {150, 0x42}, {160, 0x04}, {180, 0x24}, + {200, 0x44}, {210, 0x06}, {240, 0x26}, {250, 0x46}, + {270, 0x08}, {300, 0x28}, {330, 0x48}, {360, 0x2a}, + {400, 0x4a}, {450, 0x0c}, {500, 0x2c}, {550, 0x0e}, + {600, 0x2e}, {650, 0x10}, {700, 0x30}, {750, 0x12}, + {800, 0x32}, {850, 0x14}, {900, 0x34}, {950, 0x54}, + {1000, 0x74}, +}; + +static int max_mbps_to_hsfreqrange_sel(u32 max_mbps) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(hsfreq_map); i++) + if (hsfreq_map[i].max_mbps > max_mbps) + return hsfreq_map[i].hsfreqrange_sel; + + return -EINVAL; +} + +static int csi2_dphy_init(struct csi2_dev *csi2) +{ + struct v4l2_ctrl *ctrl; + u32 mbps_per_lane; + int sel; + + ctrl = v4l2_ctrl_find(csi2->src_sd->ctrl_handler, + V4L2_CID_LINK_FREQ); + if (!ctrl) + mbps_per_lane = CSI2_DEFAULT_MAX_MBPS; + else + mbps_per_lane = DIV_ROUND_UP_ULL(2 * ctrl->qmenu_int[ctrl->val], + USEC_PER_SEC); + + sel = max_mbps_to_hsfreqrange_sel(mbps_per_lane); + if (sel < 0) + return sel; + + dw_mipi_csi2_phy_write(csi2, 0x44, sel); + + return 0; +} + +/* + * Waits for ultra-low-power state on D-PHY clock lane. This is currently + * unused and may not be needed at all, but keep around just in case. + */ +static int __maybe_unused csi2_dphy_wait_ulp(struct csi2_dev *csi2) +{ + u32 reg; + int ret; + + /* wait for ULP on clock lane */ + ret = readl_poll_timeout(csi2->base + CSI2_PHY_STATE, reg, + !(reg & PHY_RXULPSCLKNOT), 0, 500000); + if (ret) { + v4l2_err(&csi2->sd, "ULP timeout, phy_state = 0x%08x\n", reg); + return ret; + } + + /* wait until no errors on bus */ + ret = readl_poll_timeout(csi2->base + CSI2_ERR1, reg, + reg == 0x0, 0, 500000); + if (ret) { + v4l2_err(&csi2->sd, "stable bus timeout, err1 = 0x%08x\n", reg); + return ret; + } + + return 0; +} + +/* Waits for low-power LP-11 state on data and clock lanes. */ +static int csi2_dphy_wait_stopstate(struct csi2_dev *csi2) +{ + u32 mask, reg; + int ret; + + mask = PHY_STOPSTATECLK | + ((csi2->bus.num_data_lanes - 1) << PHY_STOPSTATEDATA_BIT); + + ret = readl_poll_timeout(csi2->base + CSI2_PHY_STATE, reg, + (reg & mask) == mask, 0, 500000); + if (ret) { + v4l2_err(&csi2->sd, "LP-11 timeout, phy_state = 0x%08x\n", reg); + return ret; + } + + return 0; +} + +/* Wait for active clock on the clock lane. */ +static int csi2_dphy_wait_clock_lane(struct csi2_dev *csi2) +{ + u32 reg; + int ret; + + ret = readl_poll_timeout(csi2->base + CSI2_PHY_STATE, reg, + (reg & PHY_RXCLKACTIVEHS), 0, 500000); + if (ret) { + v4l2_err(&csi2->sd, "clock lane timeout, phy_state = 0x%08x\n", + reg); + return ret; + } + + return 0; +} + +/* Setup the i.MX CSI2IPU Gasket */ +static void csi2ipu_gasket_init(struct csi2_dev *csi2) +{ + u32 reg = 0; + + switch (csi2->format_mbus.code) { + case MEDIA_BUS_FMT_YUYV8_2X8: + case MEDIA_BUS_FMT_YUYV8_1X16: + reg = CSI2IPU_YUV422_YUYV; + break; + default: + break; + } + + writel(reg, csi2->base + CSI2IPU_GASKET); +} + +static int csi2_start(struct csi2_dev *csi2) +{ + int ret; + + ret = clk_prepare_enable(csi2->pix_clk); + if (ret) + return ret; + + /* setup the gasket */ + csi2ipu_gasket_init(csi2); + + /* Step 3 */ + ret = csi2_dphy_init(csi2); + if (ret) + goto err_disable_clk; + + /* Step 4 */ + csi2_set_lanes(csi2); + csi2_enable(csi2, true); + + /* Step 5 */ + ret = csi2_dphy_wait_stopstate(csi2); + if (ret) + goto err_assert_reset; + + /* Step 6 */ + ret = v4l2_subdev_call(csi2->src_sd, video, s_stream, 1); + ret = (ret && ret != -ENOIOCTLCMD) ? ret : 0; + if (ret) + goto err_assert_reset; + + /* Step 7 */ + ret = csi2_dphy_wait_clock_lane(csi2); + if (ret) + goto err_stop_upstream; + + return 0; + +err_stop_upstream: + v4l2_subdev_call(csi2->src_sd, video, s_stream, 0); +err_assert_reset: + csi2_enable(csi2, false); +err_disable_clk: + clk_disable_unprepare(csi2->pix_clk); + return ret; +} + +static void csi2_stop(struct csi2_dev *csi2) +{ + /* stop upstream */ + v4l2_subdev_call(csi2->src_sd, video, s_stream, 0); + + csi2_enable(csi2, false); + clk_disable_unprepare(csi2->pix_clk); +} + +/* + * V4L2 subdev operations. + */ + +static int csi2_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct csi2_dev *csi2 = sd_to_dev(sd); + int i, ret = 0; + + mutex_lock(&csi2->lock); + + if (!csi2->src_sd) { + ret = -EPIPE; + goto out; + } + + for (i = 0; i < CSI2_NUM_SRC_PADS; i++) { + if (csi2->sink_linked[i]) + break; + } + if (i >= CSI2_NUM_SRC_PADS) { + ret = -EPIPE; + goto out; + } + + /* + * enable/disable streaming only if stream_count is + * going from 0 to 1 / 1 to 0. + */ + if (csi2->stream_count != !enable) + goto update_count; + + dev_dbg(csi2->dev, "stream %s\n", enable ? "ON" : "OFF"); + if (enable) + ret = csi2_start(csi2); + else + csi2_stop(csi2); + if (ret) + goto out; + +update_count: + csi2->stream_count += enable ? 1 : -1; + if (csi2->stream_count < 0) + csi2->stream_count = 0; +out: + mutex_unlock(&csi2->lock); + return ret; +} + +static int csi2_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); + struct csi2_dev *csi2 = sd_to_dev(sd); + struct v4l2_subdev *remote_sd; + int ret = 0; + + dev_dbg(csi2->dev, "link setup %s -> %s", remote->entity->name, + local->entity->name); + + remote_sd = media_entity_to_v4l2_subdev(remote->entity); + + mutex_lock(&csi2->lock); + + if (local->flags & MEDIA_PAD_FL_SOURCE) { + if (flags & MEDIA_LNK_FL_ENABLED) { + if (csi2->sink_linked[local->index - 1]) { + ret = -EBUSY; + goto out; + } + csi2->sink_linked[local->index - 1] = true; + } else { + csi2->sink_linked[local->index - 1] = false; + } + } else { + if (flags & MEDIA_LNK_FL_ENABLED) { + if (csi2->src_sd) { + ret = -EBUSY; + goto out; + } + csi2->src_sd = remote_sd; + } else { + csi2->src_sd = NULL; + } + } + +out: + mutex_unlock(&csi2->lock); + return ret; +} + +static int csi2_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *sdformat) +{ + struct csi2_dev *csi2 = sd_to_dev(sd); + struct v4l2_mbus_framefmt *fmt; + + mutex_lock(&csi2->lock); + + if (sdformat->which == V4L2_SUBDEV_FORMAT_TRY) + fmt = v4l2_subdev_get_try_format(&csi2->sd, cfg, + sdformat->pad); + else + fmt = &csi2->format_mbus; + + sdformat->format = *fmt; + + mutex_unlock(&csi2->lock); + + return 0; +} + +static int csi2_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *sdformat) +{ + struct csi2_dev *csi2 = sd_to_dev(sd); + int ret = 0; + + if (sdformat->pad >= CSI2_NUM_PADS) + return -EINVAL; + + mutex_lock(&csi2->lock); + + if (csi2->stream_count > 0) { + ret = -EBUSY; + goto out; + } + + /* Output pads mirror active input pad, no limits on input pads */ + if (sdformat->pad != CSI2_SINK_PAD) + sdformat->format = csi2->format_mbus; + + if (sdformat->which == V4L2_SUBDEV_FORMAT_TRY) + cfg->try_fmt = sdformat->format; + else + csi2->format_mbus = sdformat->format; +out: + mutex_unlock(&csi2->lock); + return ret; +} + +/* + * retrieve our pads parsed from the OF graph by the media device + */ +static int csi2_registered(struct v4l2_subdev *sd) +{ + struct csi2_dev *csi2 = sd_to_dev(sd); + int i, ret; + + for (i = 0; i < CSI2_NUM_PADS; i++) { + csi2->pad[i].flags = (i == CSI2_SINK_PAD) ? + MEDIA_PAD_FL_SINK : MEDIA_PAD_FL_SOURCE; + } + + /* set a default mbus format */ + ret = imx_media_init_mbus_fmt(&csi2->format_mbus, + 640, 480, 0, V4L2_FIELD_NONE, NULL); + if (ret) + return ret; + + return media_entity_pads_init(&sd->entity, CSI2_NUM_PADS, csi2->pad); +} + +static const struct media_entity_operations csi2_entity_ops = { + .link_setup = csi2_link_setup, + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct v4l2_subdev_video_ops csi2_video_ops = { + .s_stream = csi2_s_stream, +}; + +static const struct v4l2_subdev_pad_ops csi2_pad_ops = { + .get_fmt = csi2_get_fmt, + .set_fmt = csi2_set_fmt, +}; + +static const struct v4l2_subdev_ops csi2_subdev_ops = { + .video = &csi2_video_ops, + .pad = &csi2_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops csi2_internal_ops = { + .registered = csi2_registered, +}; + +static int csi2_parse_endpoints(struct csi2_dev *csi2) +{ + struct device_node *node = csi2->dev->of_node; + struct device_node *epnode; + struct v4l2_fwnode_endpoint ep; + + epnode = of_graph_get_endpoint_by_regs(node, 0, -1); + if (!epnode) { + v4l2_err(&csi2->sd, "failed to get sink endpoint node\n"); + return -EINVAL; + } + + v4l2_fwnode_endpoint_parse(of_fwnode_handle(epnode), &ep); + of_node_put(epnode); + + if (ep.bus_type != V4L2_MBUS_CSI2) { + v4l2_err(&csi2->sd, "invalid bus type, must be MIPI CSI2\n"); + return -EINVAL; + } + + csi2->bus = ep.bus.mipi_csi2; + + dev_dbg(csi2->dev, "data lanes: %d\n", csi2->bus.num_data_lanes); + dev_dbg(csi2->dev, "flags: 0x%08x\n", csi2->bus.flags); + return 0; +} + +static int csi2_probe(struct platform_device *pdev) +{ + struct csi2_dev *csi2; + struct resource *res; + int ret; + + csi2 = devm_kzalloc(&pdev->dev, sizeof(*csi2), GFP_KERNEL); + if (!csi2) + return -ENOMEM; + + csi2->dev = &pdev->dev; + + v4l2_subdev_init(&csi2->sd, &csi2_subdev_ops); + v4l2_set_subdevdata(&csi2->sd, &pdev->dev); + csi2->sd.internal_ops = &csi2_internal_ops; + csi2->sd.entity.ops = &csi2_entity_ops; + csi2->sd.dev = &pdev->dev; + csi2->sd.owner = THIS_MODULE; + csi2->sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE; + strcpy(csi2->sd.name, DEVICE_NAME); + csi2->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + csi2->sd.grp_id = IMX_MEDIA_GRP_ID_CSI2; + + ret = csi2_parse_endpoints(csi2); + if (ret) + return ret; + + csi2->pllref_clk = devm_clk_get(&pdev->dev, "ref"); + if (IS_ERR(csi2->pllref_clk)) { + v4l2_err(&csi2->sd, "failed to get pll reference clock\n"); + ret = PTR_ERR(csi2->pllref_clk); + return ret; + } + + csi2->dphy_clk = devm_clk_get(&pdev->dev, "dphy"); + if (IS_ERR(csi2->dphy_clk)) { + v4l2_err(&csi2->sd, "failed to get dphy clock\n"); + ret = PTR_ERR(csi2->dphy_clk); + return ret; + } + + csi2->pix_clk = devm_clk_get(&pdev->dev, "pix"); + if (IS_ERR(csi2->pix_clk)) { + v4l2_err(&csi2->sd, "failed to get pixel clock\n"); + ret = PTR_ERR(csi2->pix_clk); + return ret; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + v4l2_err(&csi2->sd, "failed to get platform resources\n"); + return -ENODEV; + } + + csi2->base = devm_ioremap(&pdev->dev, res->start, PAGE_SIZE); + if (!csi2->base) { + v4l2_err(&csi2->sd, "failed to map CSI-2 registers\n"); + return -ENOMEM; + } + + mutex_init(&csi2->lock); + + ret = clk_prepare_enable(csi2->pllref_clk); + if (ret) { + v4l2_err(&csi2->sd, "failed to enable pllref_clk\n"); + goto rmmutex; + } + + ret = clk_prepare_enable(csi2->dphy_clk); + if (ret) { + v4l2_err(&csi2->sd, "failed to enable dphy_clk\n"); + goto pllref_off; + } + + platform_set_drvdata(pdev, &csi2->sd); + + ret = v4l2_async_register_subdev(&csi2->sd); + if (ret) + goto dphy_off; + + return 0; + +dphy_off: + clk_disable_unprepare(csi2->dphy_clk); +pllref_off: + clk_disable_unprepare(csi2->pllref_clk); +rmmutex: + mutex_destroy(&csi2->lock); + return ret; +} + +static int csi2_remove(struct platform_device *pdev) +{ + struct v4l2_subdev *sd = platform_get_drvdata(pdev); + struct csi2_dev *csi2 = sd_to_dev(sd); + + v4l2_async_unregister_subdev(sd); + clk_disable_unprepare(csi2->dphy_clk); + clk_disable_unprepare(csi2->pllref_clk); + mutex_destroy(&csi2->lock); + media_entity_cleanup(&sd->entity); + + return 0; +} + +static const struct of_device_id csi2_dt_ids[] = { + { .compatible = "fsl,imx6-mipi-csi2", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, csi2_dt_ids); + +static struct platform_driver csi2_driver = { + .driver = { + .name = DEVICE_NAME, + .of_match_table = csi2_dt_ids, + }, + .probe = csi2_probe, + .remove = csi2_remove, +}; + +module_platform_driver(csi2_driver); + +MODULE_DESCRIPTION("i.MX5/6 MIPI CSI-2 Receiver driver"); +MODULE_AUTHOR("Steve Longerbeam <steve_longerbeam@mentor.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/media/lirc/TODO b/drivers/staging/media/lirc/TODO index cbea5d84fed3..a97800a8e127 100644 --- a/drivers/staging/media/lirc/TODO +++ b/drivers/staging/media/lirc/TODO @@ -1,13 +1,36 @@ -- All drivers should either be ported to ir-core, or dropped entirely - (see drivers/media/IR/mceusb.c vs. lirc_mceusb.c in lirc cvs for an - example of a previously completed port). - -- lirc_bt829 uses registers on a Mach64 VT, which has a separate kernel - framebuffer driver (atyfb) and userland X driver (mach64). It can't - simply be converted to a normal PCI driver, but ideally it should be - coordinated with the other drivers. - -Please send patches to: -Jarod Wilson <jarod@wilsonet.com> -Greg Kroah-Hartman <greg@kroah.com> +1. Both ir-kbd-i2c and lirc_zilog provide support for RX events for +the chips supported by lirc_zilog. Before moving lirc_zilog out of staging: + +a. ir-kbd-i2c needs a module parameter added to allow the user to tell + ir-kbd-i2c to ignore Z8 IR units. + +b. lirc_zilog should provide Rx key presses to the rc core like ir-kbd-i2c + does. + + +2. lirc_zilog module ref-counting need examination. It has not been +verified that cdev and lirc_dev will take the proper module references on +lirc_zilog to prevent removal of lirc_zilog when the /dev/lircN device node +is open. + +(The good news is ref-counting of lirc_zilog internal structures appears to be +complete. Testing has shown the cx18 module can be unloaded out from under +irw + lircd + lirc_dev, with the /dev/lirc0 device node open, with no adverse +effects. The cx18 module could then be reloaded and irw properly began +receiving button presses again and ir_send worked without error.) + + +3. Bridge drivers, if able, should provide a chip reset() callback +to lirc_zilog via struct IR_i2c_init_data. cx18 and ivtv already have routines +to perform Z8 chip resets via GPIO manipulations. This would allow lirc_zilog +to bring the chip back to normal when it hangs, in the same places the +original lirc_pvr150 driver code does. This is not strictly needed, so it +is not required to move lirc_zilog out of staging. + +Note: Both lirc_zilog and ir-kbd-i2c support the Zilog Z8 for IR, as programmed +and installed on Hauppauge products. When working on either module, developers +must consider at least the following bridge drivers which mention an IR Rx unit +at address 0x71 (indicative of a Z8): + + ivtv cx18 hdpvr pvrusb2 bt8xx cx88 saa7134 diff --git a/drivers/staging/media/lirc/TODO.lirc_zilog b/drivers/staging/media/lirc/TODO.lirc_zilog deleted file mode 100644 index a97800a8e127..000000000000 --- a/drivers/staging/media/lirc/TODO.lirc_zilog +++ /dev/null @@ -1,36 +0,0 @@ -1. Both ir-kbd-i2c and lirc_zilog provide support for RX events for -the chips supported by lirc_zilog. Before moving lirc_zilog out of staging: - -a. ir-kbd-i2c needs a module parameter added to allow the user to tell - ir-kbd-i2c to ignore Z8 IR units. - -b. lirc_zilog should provide Rx key presses to the rc core like ir-kbd-i2c - does. - - -2. lirc_zilog module ref-counting need examination. It has not been -verified that cdev and lirc_dev will take the proper module references on -lirc_zilog to prevent removal of lirc_zilog when the /dev/lircN device node -is open. - -(The good news is ref-counting of lirc_zilog internal structures appears to be -complete. Testing has shown the cx18 module can be unloaded out from under -irw + lircd + lirc_dev, with the /dev/lirc0 device node open, with no adverse -effects. The cx18 module could then be reloaded and irw properly began -receiving button presses again and ir_send worked without error.) - - -3. Bridge drivers, if able, should provide a chip reset() callback -to lirc_zilog via struct IR_i2c_init_data. cx18 and ivtv already have routines -to perform Z8 chip resets via GPIO manipulations. This would allow lirc_zilog -to bring the chip back to normal when it hangs, in the same places the -original lirc_pvr150 driver code does. This is not strictly needed, so it -is not required to move lirc_zilog out of staging. - -Note: Both lirc_zilog and ir-kbd-i2c support the Zilog Z8 for IR, as programmed -and installed on Hauppauge products. When working on either module, developers -must consider at least the following bridge drivers which mention an IR Rx unit -at address 0x71 (indicative of a Z8): - - ivtv cx18 hdpvr pvrusb2 bt8xx cx88 saa7134 - diff --git a/drivers/staging/media/lirc/lirc_zilog.c b/drivers/staging/media/lirc/lirc_zilog.c index 8ce1db04414a..015e41bd036e 100644 --- a/drivers/staging/media/lirc/lirc_zilog.c +++ b/drivers/staging/media/lirc/lirc_zilog.c @@ -156,7 +156,6 @@ static struct mutex tx_data_lock; /* module parameters */ static bool debug; /* debug output */ static bool tx_only; /* only handle the IR Tx function */ -static int minor = -1; /* minor number */ /* struct IR reference counting */ @@ -184,10 +183,11 @@ static void release_ir_device(struct kref *ref) * ir->open_count == 0 - happens on final close() * ir_lock, tx_ref_lock, rx_ref_lock, all released */ - if (ir->l.minor >= 0 && ir->l.minor < MAX_IRCTL_DEVICES) { + if (ir->l.minor >= 0) { lirc_unregister_driver(ir->l.minor); - ir->l.minor = MAX_IRCTL_DEVICES; + ir->l.minor = -1; } + if (kfifo_initialized(&ir->rbuf.fifo)) lirc_buffer_free(&ir->rbuf); list_del(&ir->list); @@ -215,7 +215,7 @@ static struct IR_rx *get_ir_rx(struct IR *ir) spin_lock(&ir->rx_ref_lock); rx = ir->rx; - if (rx != NULL) + if (rx) kref_get(&rx->ref); spin_unlock(&ir->rx_ref_lock); return rx; @@ -277,7 +277,7 @@ static struct IR_tx *get_ir_tx(struct IR *ir) spin_lock(&ir->tx_ref_lock); tx = ir->tx; - if (tx != NULL) + if (tx) kref_get(&tx->ref); spin_unlock(&ir->tx_ref_lock); return tx; @@ -327,12 +327,12 @@ static int add_to_buf(struct IR *ir) } rx = get_ir_rx(ir); - if (rx == NULL) + if (!rx) return -ENXIO; /* Ensure our rx->c i2c_client remains valid for the duration */ mutex_lock(&rx->client_lock); - if (rx->c == NULL) { + if (!rx->c) { mutex_unlock(&rx->client_lock); put_ir_rx(rx, false); return -ENXIO; @@ -388,7 +388,7 @@ static int add_to_buf(struct IR *ir) break; } schedule_timeout((100 * HZ + 999) / 1000); - if (tx != NULL) + if (tx) tx->need_boot = 1; ++failures; @@ -444,7 +444,7 @@ static int add_to_buf(struct IR *ir) } while (!lirc_buffer_full(rbuf)); mutex_unlock(&rx->client_lock); - if (tx != NULL) + if (tx) put_ir_tx(tx, false); put_ir_rx(rx, false); return ret; @@ -472,7 +472,7 @@ static int lirc_thread(void *arg) /* if device not opened, we can sleep half a second */ if (atomic_read(&ir->open_count) == 0) { - schedule_timeout(HZ/2); + schedule_timeout(HZ / 2); continue; } @@ -497,18 +497,9 @@ static int lirc_thread(void *arg) return 0; } -static int set_use_inc(void *data) -{ - return 0; -} - -static void set_use_dec(void *data) -{ -} - /* safe read of a uint32 (always network byte order) */ static int read_uint32(unsigned char **data, - unsigned char *endp, unsigned int *val) + unsigned char *endp, unsigned int *val) { if (*data + 4 > endp) return 0; @@ -520,7 +511,7 @@ static int read_uint32(unsigned char **data, /* safe read of a uint8 */ static int read_uint8(unsigned char **data, - unsigned char *endp, unsigned char *val) + unsigned char *endp, unsigned char *val) { if (*data + 1 > endp) return 0; @@ -530,7 +521,7 @@ static int read_uint8(unsigned char **data, /* safe skipping of N bytes */ static int skip(unsigned char **data, - unsigned char *endp, unsigned int distance) + unsigned char *endp, unsigned int distance) { if (*data + distance > endp) return 0; @@ -540,7 +531,7 @@ static int skip(unsigned char **data, /* decompress key data into the given buffer */ static int get_key_data(unsigned char *buf, - unsigned int codeset, unsigned int key) + unsigned int codeset, unsigned int key) { unsigned char *data, *endp, *diffs, *key_block; unsigned char keys, ndiffs, id; @@ -554,9 +545,9 @@ static int get_key_data(unsigned char *buf, if (!read_uint32(&data, tx_data->endp, &i)) goto corrupt; - if (i == codeset) + if (i == codeset) { break; - else if (codeset > i) { + } else if (codeset > i) { base = pos + 1; --lim; } @@ -772,7 +763,7 @@ static int fw_load(struct IR_tx *tx) /* Parse the file */ tx_data = vmalloc(sizeof(*tx_data)); - if (tx_data == NULL) { + if (!tx_data) { release_firmware(fw_entry); ret = -ENOMEM; goto out; @@ -781,7 +772,7 @@ static int fw_load(struct IR_tx *tx) /* Copy the data so hotplug doesn't get confused and timeout */ tx_data->datap = vmalloc(fw_entry->size); - if (tx_data->datap == NULL) { + if (!tx_data->datap) { release_firmware(fw_entry); vfree(tx_data); ret = -ENOMEM; @@ -810,7 +801,7 @@ static int fw_load(struct IR_tx *tx) goto corrupt; if (!read_uint32(&data, tx_data->endp, - &tx_data->num_code_sets)) + &tx_data->num_code_sets)) goto corrupt; dev_dbg(tx->ir->l.dev, "%u IR blaster codesets loaded\n", @@ -818,7 +809,7 @@ static int fw_load(struct IR_tx *tx) tx_data->code_sets = vmalloc( tx_data->num_code_sets * sizeof(char *)); - if (tx_data->code_sets == NULL) { + if (!tx_data->code_sets) { fw_unload_locked(); ret = -ENOMEM; goto out; @@ -866,12 +857,12 @@ static int fw_load(struct IR_tx *tx) * global fixed */ if (!skip(&data, tx_data->endp, - 1 + TX_BLOCK_SIZE - num_global_fixed)) + 1 + TX_BLOCK_SIZE - num_global_fixed)) goto corrupt; /* Then we have keys-1 blocks of key id+diffs */ if (!skip(&data, tx_data->endp, - (ndiffs + 1) * (keys - 1))) + (ndiffs + 1) * (keys - 1))) goto corrupt; } ret = 0; @@ -905,7 +896,7 @@ static ssize_t read(struct file *filep, char __user *outbuf, size_t n, } rx = get_ir_rx(ir); - if (rx == NULL) + if (!rx) return -ENXIO; /* @@ -990,8 +981,9 @@ static int send_code(struct IR_tx *tx, unsigned int code, unsigned int key) "failed to get data for code %u, key %u -- check lircd.conf entries\n", code, key); return ret; - } else if (ret != 0) + } else if (ret != 0) { return ret; + } /* Send the data block */ ret = send_data_block(tx, data_block); @@ -1065,7 +1057,7 @@ static int send_code(struct IR_tx *tx, unsigned int code, unsigned int key) break; dev_dbg(tx->ir->l.dev, "NAK expected: i2c_master_send failed with %d (try %d)\n", - ret, i+1); + ret, i + 1); } if (ret != 1) { dev_err(tx->ir->l.dev, @@ -1111,12 +1103,12 @@ static ssize_t write(struct file *filep, const char __user *buf, size_t n, /* Get a struct IR_tx reference */ tx = get_ir_tx(ir); - if (tx == NULL) + if (!tx) return -ENXIO; /* Ensure our tx->c i2c_client remains valid for the duration */ mutex_lock(&tx->client_lock); - if (tx->c == NULL) { + if (!tx->c) { mutex_unlock(&tx->client_lock); put_ir_tx(tx, false); return -ENXIO; @@ -1188,8 +1180,9 @@ static ssize_t write(struct file *filep, const char __user *buf, size_t n, schedule_timeout((100 * HZ + 999) / 1000); tx->need_boot = 1; ++failures; - } else + } else { i += sizeof(int); + } } /* Release i2c bus */ @@ -1212,15 +1205,15 @@ static unsigned int poll(struct file *filep, poll_table *wait) struct lirc_buffer *rbuf = ir->l.rbuf; unsigned int ret; - dev_dbg(ir->l.dev, "poll called\n"); + dev_dbg(ir->l.dev, "%s called\n", __func__); rx = get_ir_rx(ir); - if (rx == NULL) { + if (!rx) { /* * Revisit this, if our poll function ever reports writeable * status for Tx */ - dev_dbg(ir->l.dev, "poll result = POLLERR\n"); + dev_dbg(ir->l.dev, "%s result = POLLERR\n", __func__); return POLLERR; } @@ -1231,9 +1224,9 @@ static unsigned int poll(struct file *filep, poll_table *wait) poll_wait(filep, &rbuf->wait_poll, wait); /* Indicate what ops could happen immediately without blocking */ - ret = lirc_buffer_empty(rbuf) ? 0 : (POLLIN|POLLRDNORM); + ret = lirc_buffer_empty(rbuf) ? 0 : (POLLIN | POLLRDNORM); - dev_dbg(ir->l.dev, "poll result = %s\n", + dev_dbg(ir->l.dev, "%s result = %s\n", __func__, ret ? "POLLIN|POLLRDNORM" : "none"); return ret; } @@ -1255,15 +1248,15 @@ static long ioctl(struct file *filep, unsigned int cmd, unsigned long arg) result = put_user(features, uptr); break; case LIRC_GET_REC_MODE: - if (!(features&LIRC_CAN_REC_MASK)) + if (!(features & LIRC_CAN_REC_MASK)) return -ENOSYS; result = put_user(LIRC_REC2MODE - (features&LIRC_CAN_REC_MASK), + (features & LIRC_CAN_REC_MASK), uptr); break; case LIRC_SET_REC_MODE: - if (!(features&LIRC_CAN_REC_MASK)) + if (!(features & LIRC_CAN_REC_MASK)) return -ENOSYS; result = get_user(mode, uptr); @@ -1271,13 +1264,13 @@ static long ioctl(struct file *filep, unsigned int cmd, unsigned long arg) result = -EINVAL; break; case LIRC_GET_SEND_MODE: - if (!(features&LIRC_CAN_SEND_MASK)) + if (!(features & LIRC_CAN_SEND_MASK)) return -ENOSYS; result = put_user(LIRC_MODE_PULSE, uptr); break; case LIRC_SET_SEND_MODE: - if (!(features&LIRC_CAN_SEND_MASK)) + if (!(features & LIRC_CAN_SEND_MASK)) return -ENOSYS; result = get_user(mode, uptr); @@ -1322,7 +1315,7 @@ static int open(struct inode *node, struct file *filep) /* find our IR struct */ ir = get_ir_device_by_minor(minor); - if (ir == NULL) + if (!ir) return -ENODEV; atomic_inc(&ir->open_count); @@ -1340,8 +1333,9 @@ static int close(struct inode *node, struct file *filep) /* find our IR struct */ struct IR *ir = filep->private_data; - if (ir == NULL) { - pr_err("ir: close: no private_data attached to the file!\n"); + if (!ir) { + pr_err("ir: %s: no private_data attached to the file!\n", + __func__); return -ENODEV; } @@ -1394,10 +1388,7 @@ static struct lirc_driver lirc_template = { .minor = -1, .code_length = 13, .buffer_size = BUFLEN / 2, - .sample_rate = 0, /* tell lirc_dev to not start its own kthread */ .chunk_size = 2, - .set_use_inc = set_use_inc, - .set_use_dec = set_use_dec, .fops = &lirc_fops, .owner = THIS_MODULE, }; @@ -1407,7 +1398,7 @@ static int ir_remove(struct i2c_client *client) if (strncmp("ir_tx_z8", client->name, 8) == 0) { struct IR_tx *tx = i2c_get_clientdata(client); - if (tx != NULL) { + if (tx) { mutex_lock(&tx->client_lock); tx->c = NULL; mutex_unlock(&tx->client_lock); @@ -1416,7 +1407,7 @@ static int ir_remove(struct i2c_client *client) } else if (strncmp("ir_rx_z8", client->name, 8) == 0) { struct IR_rx *rx = i2c_get_clientdata(client); - if (rx != NULL) { + if (rx) { mutex_lock(&rx->client_lock); rx->c = NULL; mutex_unlock(&rx->client_lock); @@ -1426,7 +1417,6 @@ static int ir_remove(struct i2c_client *client) return 0; } - /* ir_devices_lock must be held */ static struct IR *get_ir_device_by_adapter(struct i2c_adapter *adapter) { @@ -1467,14 +1457,14 @@ static int ir_probe(struct i2c_client *client, const struct i2c_device_id *id) return -ENXIO; pr_info("probing IR %s on %s (i2c-%d)\n", - tx_probe ? "Tx" : "Rx", adap->name, adap->nr); + tx_probe ? "Tx" : "Rx", adap->name, adap->nr); mutex_lock(&ir_devices_lock); /* Use a single struct IR instance for both the Rx and Tx functions */ ir = get_ir_device_by_adapter(adap); - if (ir == NULL) { - ir = kzalloc(sizeof(struct IR), GFP_KERNEL); + if (!ir) { + ir = kzalloc(sizeof(*ir), GFP_KERNEL); if (!ir) { ret = -ENOMEM; goto out_no_ir; @@ -1514,7 +1504,7 @@ static int ir_probe(struct i2c_client *client, const struct i2c_device_id *id) rx = get_ir_rx(ir); /* Set up a struct IR_tx instance */ - tx = kzalloc(sizeof(struct IR_tx), GFP_KERNEL); + tx = kzalloc(sizeof(*tx), GFP_KERNEL); if (!tx) { ret = -ENOMEM; goto out_put_xx; @@ -1547,7 +1537,7 @@ static int ir_probe(struct i2c_client *client, const struct i2c_device_id *id) fw_load(tx); /* Proceed only if the Rx client is also ready or not needed */ - if (rx == NULL && !tx_only) { + if (!rx && !tx_only) { dev_info(tx->ir->l.dev, "probe of IR Tx on %s (i2c-%d) done. Waiting on IR Rx.\n", adap->name, adap->nr); @@ -1558,7 +1548,7 @@ static int ir_probe(struct i2c_client *client, const struct i2c_device_id *id) tx = get_ir_tx(ir); /* Set up a struct IR_rx instance */ - rx = kzalloc(sizeof(struct IR_rx), GFP_KERNEL); + rx = kzalloc(sizeof(*rx), GFP_KERNEL); if (!rx) { ret = -ENOMEM; goto out_put_xx; @@ -1601,20 +1591,19 @@ static int ir_probe(struct i2c_client *client, const struct i2c_device_id *id) } /* Proceed only if the Tx client is also ready */ - if (tx == NULL) { + if (!tx) { pr_info("probe of IR Rx on %s (i2c-%d) done. Waiting on IR Tx.\n", - adap->name, adap->nr); + adap->name, adap->nr); goto out_ok; } } /* register with lirc */ - ir->l.minor = minor; /* module option: user requested minor number */ ir->l.minor = lirc_register_driver(&ir->l); - if (ir->l.minor < 0 || ir->l.minor >= MAX_IRCTL_DEVICES) { + if (ir->l.minor < 0) { dev_err(tx->ir->l.dev, - "%s: \"minor\" must be between 0 and %d (%d)!\n", - __func__, MAX_IRCTL_DEVICES-1, ir->l.minor); + "%s: lirc_register_driver() failed: %i\n", + __func__, ir->l.minor); ret = -EBADRQC; goto out_put_xx; } @@ -1623,9 +1612,9 @@ static int ir_probe(struct i2c_client *client, const struct i2c_device_id *id) adap->name, adap->nr, ir->l.minor); out_ok: - if (rx != NULL) + if (rx) put_ir_rx(rx, true); - if (tx != NULL) + if (tx) put_ir_tx(tx, true); put_ir_device(ir, true); dev_info(ir->l.dev, @@ -1635,10 +1624,10 @@ out_ok: return 0; out_put_xx: - if (rx != NULL) + if (rx) put_ir_rx(rx, true); out_put_tx: - if (tx != NULL) + if (tx) put_ir_tx(tx, true); out_put_ir: put_ir_device(ir, true); @@ -1686,9 +1675,6 @@ MODULE_LICENSE("GPL"); /* for compat with old name, which isn't all that accurate anymore */ MODULE_ALIAS("lirc_pvr150"); -module_param(minor, int, 0444); -MODULE_PARM_DESC(minor, "Preferred minor device number"); - module_param(debug, bool, 0644); MODULE_PARM_DESC(debug, "Enable debugging messages"); |