diff options
Diffstat (limited to 'drivers')
157 files changed, 13242 insertions, 8405 deletions
diff --git a/drivers/media/Kconfig b/drivers/media/Kconfig index a8518fb3bca7..052dcf77174b 100644 --- a/drivers/media/Kconfig +++ b/drivers/media/Kconfig @@ -80,6 +80,9 @@ config MEDIA_RC_SUPPORT Say Y when you have a TV or an IR device. +config MEDIA_CEC_EDID + tristate + # # Media controller # Selectable only for webcam/grabbers, as other drivers don't use it diff --git a/drivers/media/Makefile b/drivers/media/Makefile index e608bbce0c35..b56f013b78c3 100644 --- a/drivers/media/Makefile +++ b/drivers/media/Makefile @@ -2,6 +2,8 @@ # Makefile for the kernel multimedia device drivers. # +obj-$(CONFIG_MEDIA_CEC_EDID) += cec-edid.o + media-objs := media-device.o media-devnode.o media-entity.o # diff --git a/drivers/media/cec-edid.c b/drivers/media/cec-edid.c new file mode 100644 index 000000000000..70018247bdda --- /dev/null +++ b/drivers/media/cec-edid.c @@ -0,0 +1,168 @@ +/* + * cec-edid - HDMI Consumer Electronics Control EDID & CEC helper functions + * + * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * This program is free software; you may redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <media/cec-edid.h> + +/* + * This EDID is expected to be a CEA-861 compliant, which means that there are + * at least two blocks and one or more of the extensions blocks are CEA-861 + * blocks. + * + * The returned location is guaranteed to be < size - 1. + */ +static unsigned int cec_get_edid_spa_location(const u8 *edid, unsigned int size) +{ + unsigned int blocks = size / 128; + unsigned int block; + u8 d; + + /* Sanity check: at least 2 blocks and a multiple of the block size */ + if (blocks < 2 || size % 128) + return 0; + + /* + * If there are fewer extension blocks than the size, then update + * 'blocks'. It is allowed to have more extension blocks than the size, + * since some hardware can only read e.g. 256 bytes of the EDID, even + * though more blocks are present. The first CEA-861 extension block + * should normally be in block 1 anyway. + */ + if (edid[0x7e] + 1 < blocks) + blocks = edid[0x7e] + 1; + + for (block = 1; block < blocks; block++) { + unsigned int offset = block * 128; + + /* Skip any non-CEA-861 extension blocks */ + if (edid[offset] != 0x02 || edid[offset + 1] != 0x03) + continue; + + /* search Vendor Specific Data Block (tag 3) */ + d = edid[offset + 2] & 0x7f; + /* Check if there are Data Blocks */ + if (d <= 4) + continue; + if (d > 4) { + unsigned int i = offset + 4; + unsigned int end = offset + d; + + /* Note: 'end' is always < 'size' */ + do { + u8 tag = edid[i] >> 5; + u8 len = edid[i] & 0x1f; + + if (tag == 3 && len >= 5 && i + len <= end) + return i + 4; + i += len + 1; + } while (i < end); + } + } + return 0; +} + +u16 cec_get_edid_phys_addr(const u8 *edid, unsigned int size, + unsigned int *offset) +{ + unsigned int loc = cec_get_edid_spa_location(edid, size); + + if (offset) + *offset = loc; + if (loc == 0) + return CEC_PHYS_ADDR_INVALID; + return (edid[loc] << 8) | edid[loc + 1]; +} +EXPORT_SYMBOL_GPL(cec_get_edid_phys_addr); + +void cec_set_edid_phys_addr(u8 *edid, unsigned int size, u16 phys_addr) +{ + unsigned int loc = cec_get_edid_spa_location(edid, size); + u8 sum = 0; + unsigned int i; + + if (loc == 0) + return; + edid[loc] = phys_addr >> 8; + edid[loc + 1] = phys_addr & 0xff; + loc &= ~0x7f; + + /* update the checksum */ + for (i = loc; i < loc + 127; i++) + sum += edid[i]; + edid[i] = 256 - sum; +} +EXPORT_SYMBOL_GPL(cec_set_edid_phys_addr); + +u16 cec_phys_addr_for_input(u16 phys_addr, u8 input) +{ + /* Check if input is sane */ + if (WARN_ON(input == 0 || input > 0xf)) + return CEC_PHYS_ADDR_INVALID; + + if (phys_addr == 0) + return input << 12; + + if ((phys_addr & 0x0fff) == 0) + return phys_addr | (input << 8); + + if ((phys_addr & 0x00ff) == 0) + return phys_addr | (input << 4); + + if ((phys_addr & 0x000f) == 0) + return phys_addr | input; + + /* + * All nibbles are used so no valid physical addresses can be assigned + * to the input. + */ + return CEC_PHYS_ADDR_INVALID; +} +EXPORT_SYMBOL_GPL(cec_phys_addr_for_input); + +int cec_phys_addr_validate(u16 phys_addr, u16 *parent, u16 *port) +{ + int i; + + if (parent) + *parent = phys_addr; + if (port) + *port = 0; + if (phys_addr == CEC_PHYS_ADDR_INVALID) + return 0; + for (i = 0; i < 16; i += 4) + if (phys_addr & (0xf << i)) + break; + if (i == 16) + return 0; + if (parent) + *parent = phys_addr & (0xfff0 << i); + if (port) + *port = (phys_addr >> i) & 0xf; + for (i += 4; i < 16; i += 4) + if ((phys_addr & (0xf << i)) == 0) + return -EINVAL; + return 0; +} +EXPORT_SYMBOL_GPL(cec_phys_addr_validate); + +MODULE_AUTHOR("Hans Verkuil <hans.verkuil@cisco.com>"); +MODULE_DESCRIPTION("CEC EDID helper functions"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb-core/demux.h b/drivers/media/dvb-core/demux.h index 6d3b95b8939d..ad42252b1c66 100644 --- a/drivers/media/dvb-core/demux.h +++ b/drivers/media/dvb-core/demux.h @@ -1,6 +1,10 @@ /* * demux.h * + * The Kernel Digital TV Demux kABI defines a driver-internal interface for + * registering low-level, hardware specific driver to a hardware independent + * demux layer. + * * Copyright (c) 2002 Convergence GmbH * * based on code: @@ -32,49 +36,6 @@ #include <linux/time.h> #include <linux/dvb/dmx.h> -/** - * DOC: Digital TV Demux - * - * The Kernel Digital TV Demux kABI defines a driver-internal interface for - * registering low-level, hardware specific driver to a hardware independent - * demux layer. It is only of interest for Digital TV device driver writers. - * The header file for this kABI is named demux.h and located in - * drivers/media/dvb-core. - * - * The demux kABI should be implemented for each demux in the system. It is - * used to select the TS source of a demux and to manage the demux resources. - * When the demux client allocates a resource via the demux kABI, it receives - * a pointer to the kABI of that resource. - * - * Each demux receives its TS input from a DVB front-end or from memory, as - * set via this demux kABI. In a system with more than one front-end, the kABI - * can be used to select one of the DVB front-ends as a TS source for a demux, - * unless this is fixed in the HW platform. - * - * The demux kABI only controls front-ends regarding to their connections with - * demuxes; the kABI used to set the other front-end parameters, such as - * tuning, are devined via the Digital TV Frontend kABI. - * - * The functions that implement the abstract interface demux should be defined - * static or module private and registered to the Demux core for external - * access. It is not necessary to implement every function in the struct - * &dmx_demux. For example, a demux interface might support Section filtering, - * but not PES filtering. The kABI client is expected to check the value of any - * function pointer before calling the function: the value of NULL means - * that the function is not available. - * - * Whenever the functions of the demux API modify shared data, the - * possibilities of lost update and race condition problems should be - * addressed, e.g. by protecting parts of code with mutexes. - * - * Note that functions called from a bottom half context must not sleep. - * Even a simple memory allocation without using %GFP_ATOMIC can result in a - * kernel thread being put to sleep if swapping is needed. For example, the - * Linux Kernel calls the functions of a network device interface from a - * bottom half context. Thus, if a demux kABI function is called from network - * device code, the function must not sleep. - */ - /* * Common definitions */ @@ -231,30 +192,6 @@ struct dmx_section_feed { }; /** - * DOC: Demux Callback - * - * This kernel-space API comprises the callback functions that deliver filtered - * data to the demux client. Unlike the other DVB kABIs, these functions are - * provided by the client and called from the demux code. - * - * The function pointers of this abstract interface are not packed into a - * structure as in the other demux APIs, because the callback functions are - * registered and used independent of each other. As an example, it is possible - * for the API client to provide several callback functions for receiving TS - * packets and no callbacks for PES packets or sections. - * - * The functions that implement the callback API need not be re-entrant: when - * a demux driver calls one of these functions, the driver is not allowed to - * call the function again before the original call returns. If a callback is - * triggered by a hardware interrupt, it is recommended to use the Linux - * bottom half mechanism or start a tasklet instead of making the callback - * function call directly from a hardware interrupt. - * - * This mechanism is implemented by dmx_ts_cb() and dmx_section_cb() - * callbacks. - */ - -/** * typedef dmx_ts_cb - DVB demux TS filter callback function prototype * * @buffer1: Pointer to the start of the filtered TS packets. @@ -442,10 +379,10 @@ enum dmx_demux_caps { * @open is called and decrement it when @close is called. * The @demux function parameter contains a pointer to the demux API and * instance data. - * It returns - * 0 on success; - * -EUSERS, if maximum usage count was reached; - * -EINVAL, on bad parameter. + * It returns: + * 0 on success; + * -EUSERS, if maximum usage count was reached; + * -EINVAL, on bad parameter. * * @close: This function reserves the demux for use by the caller and, if * necessary, initializes the demux. When the demux is no longer needed, @@ -455,10 +392,10 @@ enum dmx_demux_caps { * @open is called and decrement it when @close is called. * The @demux function parameter contains a pointer to the demux API and * instance data. - * It returns - * 0 on success; - * -ENODEV, if demux was not in use (e. g. no users); - * -EINVAL, on bad parameter. + * It returns: + * 0 on success; + * -ENODEV, if demux was not in use (e. g. no users); + * -EINVAL, on bad parameter. * * @write: This function provides the demux driver with a memory buffer * containing TS packets. Instead of receiving TS packets from the DVB @@ -473,12 +410,12 @@ enum dmx_demux_caps { * The @buf function parameter contains a pointer to the TS data in * kernel-space memory. * The @count function parameter contains the length of the TS data. - * It returns - * 0 on success; - * -ERESTARTSYS, if mutex lock was interrupted; - * -EINTR, if a signal handling is pending; - * -ENODEV, if demux was removed; - * -EINVAL, on bad parameter. + * It returns: + * 0 on success; + * -ERESTARTSYS, if mutex lock was interrupted; + * -EINTR, if a signal handling is pending; + * -ENODEV, if demux was removed; + * -EINVAL, on bad parameter. * * @allocate_ts_feed: Allocates a new TS feed, which is used to filter the TS * packets carrying a certain PID. The TS feed normally corresponds to a @@ -489,11 +426,11 @@ enum dmx_demux_caps { * instance data. * The @callback function parameter contains a pointer to the callback * function for passing received TS packet. - * It returns - * 0 on success; - * -ERESTARTSYS, if mutex lock was interrupted; - * -EBUSY, if no more TS feeds is available; - * -EINVAL, on bad parameter. + * It returns: + * 0 on success; + * -ERESTARTSYS, if mutex lock was interrupted; + * -EBUSY, if no more TS feeds is available; + * -EINVAL, on bad parameter. * * @release_ts_feed: Releases the resources allocated with @allocate_ts_feed. * Any filtering in progress on the TS feed should be stopped before @@ -502,9 +439,9 @@ enum dmx_demux_caps { * instance data. * The @feed function parameter contains a pointer to the TS feed API and * instance data. - * It returns - * 0 on success; - * -EINVAL on bad parameter. + * It returns: + * 0 on success; + * -EINVAL on bad parameter. * * @allocate_section_feed: Allocates a new section feed, i.e. a demux resource * for filtering and receiving sections. On platforms with hardware @@ -520,10 +457,10 @@ enum dmx_demux_caps { * instance data. * The @callback function parameter contains a pointer to the callback * function for passing received TS packet. - * It returns - * 0 on success; - * -EBUSY, if no more TS feeds is available; - * -EINVAL, on bad parameter. + * It returns: + * 0 on success; + * -EBUSY, if no more TS feeds is available; + * -EINVAL, on bad parameter. * * @release_section_feed: Releases the resources allocated with * @allocate_section_feed, including allocated filters. Any filtering in @@ -533,9 +470,9 @@ enum dmx_demux_caps { * instance data. * The @feed function parameter contains a pointer to the TS feed API and * instance data. - * It returns - * 0 on success; - * -EINVAL, on bad parameter. + * It returns: + * 0 on success; + * -EINVAL, on bad parameter. * * @add_frontend: Registers a connectivity between a demux and a front-end, * i.e., indicates that the demux can be connected via a call to @@ -549,9 +486,9 @@ enum dmx_demux_caps { * instance data. * The @frontend function parameter contains a pointer to the front-end * instance data. - * It returns - * 0 on success; - * -EINVAL, on bad parameter. + * It returns: + * 0 on success; + * -EINVAL, on bad parameter. * * @remove_frontend: Indicates that the given front-end, registered by a call * to @add_frontend, can no longer be connected as a TS source by this @@ -565,10 +502,10 @@ enum dmx_demux_caps { * instance data. * The @frontend function parameter contains a pointer to the front-end * instance data. - * It returns - * 0 on success; - * -ENODEV, if the front-end was not found, - * -EINVAL, on bad parameter. + * It returns: + * 0 on success; + * -ENODEV, if the front-end was not found, + * -EINVAL, on bad parameter. * * @get_frontends: Provides the APIs of the front-ends that have been * registered for this demux. Any of the front-ends obtained with this @@ -592,17 +529,17 @@ enum dmx_demux_caps { * instance data. * The @frontend function parameter contains a pointer to the front-end * instance data. - * It returns - * 0 on success; - * -EINVAL, on bad parameter. + * It returns: + * 0 on success; + * -EINVAL, on bad parameter. * * @disconnect_frontend: Disconnects the demux and a front-end previously * connected by a @connect_frontend call. * The @demux function parameter contains a pointer to the demux API and * instance data. - * It returns - * 0 on success; - * -EINVAL on bad parameter. + * It returns: + * 0 on success; + * -EINVAL on bad parameter. * * @get_pes_pids: Get the PIDs for DMX_PES_AUDIO0, DMX_PES_VIDEO0, * DMX_PES_TELETEXT0, DMX_PES_SUBTITLE0 and DMX_PES_PCR0. @@ -610,9 +547,9 @@ enum dmx_demux_caps { * instance data. * The @pids function parameter contains an array with five u16 elements * where the PIDs will be stored. - * It returns - * 0 on success; - * -EINVAL on bad parameter. + * It returns: + * 0 on success; + * -EINVAL on bad parameter. */ struct dmx_demux { diff --git a/drivers/media/dvb-core/dvb_ca_en50221.c b/drivers/media/dvb-core/dvb_ca_en50221.c index f82cd1ff4f3a..b1e3a26b1431 100644 --- a/drivers/media/dvb-core/dvb_ca_en50221.c +++ b/drivers/media/dvb-core/dvb_ca_en50221.c @@ -161,6 +161,18 @@ struct dvb_ca_private { struct mutex ioctl_mutex; }; +static void dvb_ca_private_free(struct dvb_ca_private *ca) +{ + unsigned int i; + + dvb_unregister_device(ca->dvbdev); + for (i = 0; i < ca->slot_count; i++) + vfree(ca->slot_info[i].rx_buffer.data); + + kfree(ca->slot_info); + kfree(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); @@ -1759,10 +1771,7 @@ void dvb_ca_en50221_release(struct dvb_ca_en50221 *pubca) for (i = 0; i < ca->slot_count; i++) { dvb_ca_en50221_slot_shutdown(ca, i); - vfree(ca->slot_info[i].rx_buffer.data); } - kfree(ca->slot_info); - dvb_unregister_device(ca->dvbdev); - kfree(ca); + dvb_ca_private_free(ca); pubca->private = NULL; } diff --git a/drivers/media/dvb-core/dvb_frontend.h b/drivers/media/dvb-core/dvb_frontend.h index 9592573a0b41..8c551174537a 100644 --- a/drivers/media/dvb-core/dvb_frontend.h +++ b/drivers/media/dvb-core/dvb_frontend.h @@ -1,6 +1,10 @@ /* * dvb_frontend.h * + * The Digital TV Frontend kABI defines a driver-internal interface for + * registering low-level, hardware specific driver to a hardware independent + * frontend layer. + * * Copyright (C) 2001 convergence integrated media GmbH * Copyright (C) 2004 convergence GmbH * @@ -42,29 +46,6 @@ #include "dvbdev.h" -/** - * DOC: Digital TV Frontend - * - * The Digital TV Frontend kABI defines a driver-internal interface for - * registering low-level, hardware specific driver to a hardware independent - * frontend layer. It is only of interest for Digital TV device driver writers. - * The header file for this API is named dvb_frontend.h and located in - * drivers/media/dvb-core. - * - * Before using the Digital TV frontend core, the bridge driver should attach - * the frontend demod, tuner and SEC devices and call dvb_register_frontend(), - * in order to register the new frontend at the subsystem. At device - * detach/removal, the bridge driver should call dvb_unregister_frontend() to - * remove the frontend from the core and then dvb_frontend_detach() to free the - * memory allocated by the frontend drivers. - * - * The drivers should also call dvb_frontend_suspend() as part of their - * handler for the &device_driver.suspend(), and dvb_frontend_resume() as - * part of their handler for &device_driver.resume(). - * - * A few other optional functions are provided to handle some special cases. - */ - /* * Maximum number of Delivery systems per frontend. It * should be smaller or equal to 32 diff --git a/drivers/media/dvb-frontends/Kconfig b/drivers/media/dvb-frontends/Kconfig index a82f77c49bd5..c645aa81f423 100644 --- a/drivers/media/dvb-frontends/Kconfig +++ b/drivers/media/dvb-frontends/Kconfig @@ -73,6 +73,14 @@ config DVB_SI2165 Say Y when you want to support this frontend. +config DVB_MN88472 + tristate "Panasonic MN88472" + depends on DVB_CORE && I2C + select REGMAP_I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + Say Y when you want to support this frontend. + config DVB_MN88473 tristate "Panasonic MN88473" depends on DVB_CORE && I2C @@ -853,6 +861,13 @@ config DVB_ASCOT2E help Say Y when you want to support this frontend. +config DVB_HELENE + tristate "Sony HELENE Sat/Ter tuner (CXD2858ER)" + depends on DVB_CORE && I2C + default m if !MEDIA_SUBDRV_AUTOSELECT + help + Say Y when you want to support this frontend. + comment "Tools to develop new frontends" config DVB_DUMMY_FE diff --git a/drivers/media/dvb-frontends/Makefile b/drivers/media/dvb-frontends/Makefile index eb7191f4219d..e90165ad361b 100644 --- a/drivers/media/dvb-frontends/Makefile +++ b/drivers/media/dvb-frontends/Makefile @@ -95,6 +95,7 @@ obj-$(CONFIG_DVB_STV0900) += stv0900.o obj-$(CONFIG_DVB_STV090x) += stv090x.o obj-$(CONFIG_DVB_STV6110x) += stv6110x.o obj-$(CONFIG_DVB_M88DS3103) += m88ds3103.o +obj-$(CONFIG_DVB_MN88472) += mn88472.o obj-$(CONFIG_DVB_MN88473) += mn88473.o obj-$(CONFIG_DVB_ISL6423) += isl6423.o obj-$(CONFIG_DVB_EC100) += ec100.o @@ -123,3 +124,4 @@ obj-$(CONFIG_DVB_AS102_FE) += as102_fe.o obj-$(CONFIG_DVB_TC90522) += tc90522.o obj-$(CONFIG_DVB_HORUS3A) += horus3a.o obj-$(CONFIG_DVB_ASCOT2E) += ascot2e.o +obj-$(CONFIG_DVB_HELENE) += helene.o diff --git a/drivers/media/dvb-frontends/cxd2841er.c b/drivers/media/dvb-frontends/cxd2841er.c index 900186ba8e62..d369a7567d18 100644 --- a/drivers/media/dvb-frontends/cxd2841er.c +++ b/drivers/media/dvb-frontends/cxd2841er.c @@ -1,7 +1,9 @@ /* * cxd2841er.c * - * Sony CXD2441ER digital demodulator driver + * Sony digital demodulator driver for + * CXD2841ER - DVB-S/S2/T/T2/C/C2 + * CXD2854ER - DVB-S/S2/T/T2/C/C2, ISDB-T/S * * Copyright 2012 Sony Corporation * Copyright (C) 2014 NetUP Inc. @@ -51,6 +53,8 @@ struct cxd2841er_priv { const struct cxd2841er_config *config; enum cxd2841er_state state; u8 system; + enum cxd2841er_xtal xtal; + enum fe_caps caps; }; static const struct cxd2841er_cnr_data s_cn_data[] = { @@ -188,6 +192,9 @@ static const struct cxd2841er_cnr_data s2_cn_data[] = { }; #define MAKE_IFFREQ_CONFIG(iffreq) ((u32)(((iffreq)/41.0)*16777216.0 + 0.5)) +#define MAKE_IFFREQ_CONFIG_XTAL(xtal, iffreq) ((xtal == SONY_XTAL_24000) ? \ + (u32)(((iffreq)/48.0)*16777216.0 + 0.5) : \ + (u32)(((iffreq)/41.0)*16777216.0 + 0.5)) static void cxd2841er_i2c_debug(struct cxd2841er_priv *priv, u8 addr, u8 reg, u8 write, @@ -217,7 +224,7 @@ static int cxd2841er_write_regs(struct cxd2841er_priv *priv, }; if (len + 1 >= sizeof(buf)) { - dev_warn(&priv->i2c->dev,"wr reg=%04x: len=%d is too big!\n", + dev_warn(&priv->i2c->dev, "wr reg=%04x: len=%d is too big!\n", reg, len + 1); return -E2BIG; } @@ -282,6 +289,7 @@ static int cxd2841er_read_regs(struct cxd2841er_priv *priv, KBUILD_MODNAME, ret, i2c_addr, reg); return ret; } + cxd2841er_i2c_debug(priv, i2c_addr, reg, 0, val, len); return 0; } @@ -427,6 +435,15 @@ static int cxd2841er_sleep_tc_to_active_t2_band(struct cxd2841er_priv *priv, static int cxd2841er_sleep_tc_to_active_c_band(struct cxd2841er_priv *priv, u32 bandwidth); +static int cxd2841er_sleep_tc_to_active_i(struct cxd2841er_priv *priv, + u32 bandwidth); + +static int cxd2841er_active_i_to_sleep_tc(struct cxd2841er_priv *priv); + +static int cxd2841er_sleep_tc_to_shutdown(struct cxd2841er_priv *priv); + +static int cxd2841er_shutdown_to_sleep_tc(struct cxd2841er_priv *priv); + static int cxd2841er_retune_active(struct cxd2841er_priv *priv, struct dtv_frontend_properties *p) { @@ -454,7 +471,13 @@ static int cxd2841er_retune_active(struct cxd2841er_priv *priv, priv, p->bandwidth_hz); case SYS_DVBC_ANNEX_A: return cxd2841er_sleep_tc_to_active_c_band( - priv, 8000000); + priv, p->bandwidth_hz); + case SYS_ISDBT: + cxd2841er_active_i_to_sleep_tc(priv); + cxd2841er_sleep_tc_to_shutdown(priv); + cxd2841er_shutdown_to_sleep_tc(priv); + return cxd2841er_sleep_tc_to_active_i( + priv, p->bandwidth_hz); } } dev_dbg(&priv->i2c->dev, "%s(): invalid delivery system %d\n", @@ -669,6 +692,45 @@ static int cxd2841er_active_c_to_sleep_tc(struct cxd2841er_priv *priv) return 0; } +static int cxd2841er_active_i_to_sleep_tc(struct cxd2841er_priv *priv) +{ + dev_dbg(&priv->i2c->dev, "%s()\n", __func__); + if (priv->state != STATE_ACTIVE_TC) { + dev_err(&priv->i2c->dev, "%s(): invalid state %d\n", + __func__, priv->state); + return -EINVAL; + } + /* Set SLV-T Bank : 0x00 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x00); + /* disable TS output */ + cxd2841er_write_reg(priv, I2C_SLVT, 0xc3, 0x01); + /* enable Hi-Z setting 1 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x80, 0x3f); + /* enable Hi-Z setting 2 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x81, 0xff); + + /* TODO: Cancel demod parameter */ + + /* Set SLV-X Bank : 0x00 */ + cxd2841er_write_reg(priv, I2C_SLVX, 0x00, 0x00); + /* disable ADC 1 */ + cxd2841er_write_reg(priv, I2C_SLVX, 0x18, 0x01); + /* Set SLV-T Bank : 0x00 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x00); + /* Disable ADC 2 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x43, 0x0a); + /* Disable ADC 3 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x41, 0x0a); + /* Disable ADC clock */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x30, 0x00); + /* Disable RF level monitor */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x2f, 0x00); + /* Disable demod clock */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x2c, 0x00); + priv->state = STATE_SLEEP_TC; + return 0; +} + static int cxd2841er_shutdown_to_sleep_s(struct cxd2841er_priv *priv) { dev_dbg(&priv->i2c->dev, "%s()\n", __func__); @@ -686,8 +748,25 @@ static int cxd2841er_shutdown_to_sleep_s(struct cxd2841er_priv *priv) cxd2841er_write_reg(priv, I2C_SLVX, 0x00, 0x00); /* Set demod SW reset */ cxd2841er_write_reg(priv, I2C_SLVX, 0x10, 0x01); - /* Set X'tal clock to 20.5Mhz */ - cxd2841er_write_reg(priv, I2C_SLVX, 0x14, 0x00); + + switch (priv->xtal) { + case SONY_XTAL_20500: + cxd2841er_write_reg(priv, I2C_SLVX, 0x14, 0x00); + break; + case SONY_XTAL_24000: + /* Select demod frequency */ + cxd2841er_write_reg(priv, I2C_SLVX, 0x12, 0x00); + cxd2841er_write_reg(priv, I2C_SLVX, 0x14, 0x03); + break; + case SONY_XTAL_41000: + cxd2841er_write_reg(priv, I2C_SLVX, 0x14, 0x01); + break; + default: + dev_dbg(&priv->i2c->dev, "%s(): invalid demod xtal %d\n", + __func__, priv->xtal); + return -EINVAL; + } + /* Set demod mode */ cxd2841er_write_reg(priv, I2C_SLVX, 0x17, 0x0a); /* Clear demod SW reset */ @@ -712,6 +791,8 @@ static int cxd2841er_shutdown_to_sleep_s(struct cxd2841er_priv *priv) static int cxd2841er_shutdown_to_sleep_tc(struct cxd2841er_priv *priv) { + u8 data = 0; + dev_dbg(&priv->i2c->dev, "%s()\n", __func__); if (priv->state != STATE_SHUTDOWN) { dev_dbg(&priv->i2c->dev, "%s(): invalid demod state %d\n", @@ -727,9 +808,24 @@ static int cxd2841er_shutdown_to_sleep_tc(struct cxd2841er_priv *priv) cxd2841er_write_reg(priv, I2C_SLVX, 0x00, 0x00); /* Set demod SW reset */ cxd2841er_write_reg(priv, I2C_SLVX, 0x10, 0x01); - /* Set X'tal clock to 20.5Mhz */ + /* Select ADC clock mode */ cxd2841er_write_reg(priv, I2C_SLVX, 0x13, 0x00); - cxd2841er_write_reg(priv, I2C_SLVX, 0x14, 0x00); + + switch (priv->xtal) { + case SONY_XTAL_20500: + data = 0x0; + break; + case SONY_XTAL_24000: + /* Select demod frequency */ + cxd2841er_write_reg(priv, I2C_SLVX, 0x12, 0x00); + data = 0x3; + break; + case SONY_XTAL_41000: + cxd2841er_write_reg(priv, I2C_SLVX, 0x12, 0x00); + data = 0x1; + break; + } + cxd2841er_write_reg(priv, I2C_SLVX, 0x14, data); /* Clear demod SW reset */ cxd2841er_write_reg(priv, I2C_SLVX, 0x10, 0x00); usleep_range(1000, 2000); @@ -809,11 +905,14 @@ static void cxd2841er_set_ts_clock_mode(struct cxd2841er_priv *priv, static u8 cxd2841er_chip_id(struct cxd2841er_priv *priv) { - u8 chip_id; + u8 chip_id = 0; dev_dbg(&priv->i2c->dev, "%s()\n", __func__); - cxd2841er_write_reg(priv, I2C_SLVT, 0, 0); - cxd2841er_read_reg(priv, I2C_SLVT, 0xfd, &chip_id); + if (cxd2841er_write_reg(priv, I2C_SLVT, 0, 0) == 0) + cxd2841er_read_reg(priv, I2C_SLVT, 0xfd, &chip_id); + else if (cxd2841er_write_reg(priv, I2C_SLVX, 0, 0) == 0) + cxd2841er_read_reg(priv, I2C_SLVX, 0xfd, &chip_id); + return chip_id; } @@ -896,6 +995,25 @@ static int cxd2841er_read_status_c(struct cxd2841er_priv *priv, u8 *tslock) return 0; } +static int cxd2841er_read_status_i(struct cxd2841er_priv *priv, + u8 *sync, u8 *tslock, u8 *unlock) +{ + u8 data = 0; + + dev_dbg(&priv->i2c->dev, "%s()\n", __func__); + if (priv->state != STATE_ACTIVE_TC) + return -EINVAL; + /* Set SLV-T Bank : 0x60 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x60); + cxd2841er_read_reg(priv, I2C_SLVT, 0x10, &data); + dev_dbg(&priv->i2c->dev, + "%s(): lock=0x%x\n", __func__, data); + *sync = ((data & 0x02) ? 1 : 0); + *tslock = ((data & 0x01) ? 1 : 0); + *unlock = ((data & 0x10) ? 1 : 0); + return 0; +} + static int cxd2841er_read_status_tc(struct dvb_frontend *fe, enum fe_status *status) { @@ -921,6 +1039,20 @@ static int cxd2841er_read_status_tc(struct dvb_frontend *fe, FE_HAS_SYNC; if (tslock) *status |= FE_HAS_LOCK; + } else if (priv->system == SYS_ISDBT) { + ret = cxd2841er_read_status_i( + priv, &sync, &tslock, &unlock); + if (ret) + goto done; + if (unlock) + goto done; + if (sync) + *status = FE_HAS_SIGNAL | + FE_HAS_CARRIER | + FE_HAS_VITERBI | + FE_HAS_SYNC; + if (tslock) + *status |= FE_HAS_LOCK; } else if (priv->system == SYS_DVBC_ANNEX_A) { ret = cxd2841er_read_status_c(priv, &tslock); if (ret) @@ -997,6 +1129,76 @@ static int cxd2841er_get_carrier_offset_s_s2(struct cxd2841er_priv *priv, return 0; } +static int cxd2841er_get_carrier_offset_i(struct cxd2841er_priv *priv, + u32 bandwidth, int *offset) +{ + u8 data[4]; + + dev_dbg(&priv->i2c->dev, "%s()\n", __func__); + if (priv->state != STATE_ACTIVE_TC) { + dev_dbg(&priv->i2c->dev, "%s(): invalid state %d\n", + __func__, priv->state); + return -EINVAL; + } + if (priv->system != SYS_ISDBT) { + dev_dbg(&priv->i2c->dev, "%s(): invalid delivery system %d\n", + __func__, priv->system); + return -EINVAL; + } + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x60); + cxd2841er_read_regs(priv, I2C_SLVT, 0x4c, data, sizeof(data)); + *offset = -1 * sign_extend32( + ((u32)(data[0] & 0x1F) << 24) | ((u32)data[1] << 16) | + ((u32)data[2] << 8) | (u32)data[3], 29); + + switch (bandwidth) { + case 6000000: + *offset = -1 * ((*offset) * 8/264); + break; + case 7000000: + *offset = -1 * ((*offset) * 8/231); + break; + case 8000000: + *offset = -1 * ((*offset) * 8/198); + break; + default: + dev_dbg(&priv->i2c->dev, "%s(): invalid bandwidth %d\n", + __func__, bandwidth); + return -EINVAL; + } + + dev_dbg(&priv->i2c->dev, "%s(): bandwidth %d offset %d\n", + __func__, bandwidth, *offset); + + return 0; +} + +static int cxd2841er_get_carrier_offset_t(struct cxd2841er_priv *priv, + u32 bandwidth, int *offset) +{ + u8 data[4]; + + dev_dbg(&priv->i2c->dev, "%s()\n", __func__); + if (priv->state != STATE_ACTIVE_TC) { + dev_dbg(&priv->i2c->dev, "%s(): invalid state %d\n", + __func__, priv->state); + return -EINVAL; + } + if (priv->system != SYS_DVBT) { + dev_dbg(&priv->i2c->dev, "%s(): invalid delivery system %d\n", + __func__, priv->system); + return -EINVAL; + } + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x10); + cxd2841er_read_regs(priv, I2C_SLVT, 0x4c, data, sizeof(data)); + *offset = -1 * sign_extend32( + ((u32)(data[0] & 0x1F) << 24) | ((u32)data[1] << 16) | + ((u32)data[2] << 8) | (u32)data[3], 29); + *offset *= (bandwidth / 1000000); + *offset /= 235; + return 0; +} + static int cxd2841er_get_carrier_offset_t2(struct cxd2841er_priv *priv, u32 bandwidth, int *offset) { @@ -1096,6 +1298,38 @@ static int cxd2841er_read_packet_errors_t2( return 0; } +static int cxd2841er_read_packet_errors_i( + struct cxd2841er_priv *priv, u32 *penum) +{ + u8 data[2]; + + *penum = 0; + if (priv->state != STATE_ACTIVE_TC) { + dev_dbg(&priv->i2c->dev, "%s(): invalid state %d\n", + __func__, priv->state); + return -EINVAL; + } + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x60); + cxd2841er_read_regs(priv, I2C_SLVT, 0xA1, data, 1); + + if (!(data[0] & 0x01)) + return 0; + + /* Layer A */ + cxd2841er_read_regs(priv, I2C_SLVT, 0xA2, data, sizeof(data)); + *penum = ((u32)data[0] << 8) | (u32)data[1]; + + /* Layer B */ + cxd2841er_read_regs(priv, I2C_SLVT, 0xA4, data, sizeof(data)); + *penum += ((u32)data[0] << 8) | (u32)data[1]; + + /* Layer C */ + cxd2841er_read_regs(priv, I2C_SLVT, 0xA6, data, sizeof(data)); + *penum += ((u32)data[0] << 8) | (u32)data[1]; + + return 0; +} + static u32 cxd2841er_mon_read_ber_s(struct cxd2841er_priv *priv) { u8 data[11]; @@ -1391,6 +1625,37 @@ static int cxd2841er_read_snr_t2(struct cxd2841er_priv *priv, u32 *snr) return 0; } +static int cxd2841er_read_snr_i(struct cxd2841er_priv *priv, u32 *snr) +{ + u32 reg; + u8 data[2]; + + *snr = 0; + if (priv->state != STATE_ACTIVE_TC) { + dev_dbg(&priv->i2c->dev, + "%s(): invalid state %d\n", __func__, + priv->state); + return -EINVAL; + } + + /* Freeze all registers */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x01, 0x01); + + + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x60); + cxd2841er_read_regs(priv, I2C_SLVT, 0x28, data, sizeof(data)); + reg = ((u32)data[0] << 8) | (u32)data[1]; + if (reg == 0) { + dev_dbg(&priv->i2c->dev, + "%s(): reg value out of range\n", __func__); + return 0; + } + if (reg > 4996) + reg = 4996; + *snr = 100 * intlog10(reg) - 9031; + return 0; +} + static u16 cxd2841er_read_agc_gain_t_t2(struct cxd2841er_priv *priv, u8 delsys) { @@ -1399,6 +1664,26 @@ static u16 cxd2841er_read_agc_gain_t_t2(struct cxd2841er_priv *priv, cxd2841er_write_reg( priv, I2C_SLVT, 0x00, (delsys == SYS_DVBT ? 0x10 : 0x20)); cxd2841er_read_regs(priv, I2C_SLVT, 0x26, data, 2); + dev_dbg(&priv->i2c->dev, + "%s(): AGC value=%u\n", + __func__, (((u16)data[0] & 0x0F) << 8) | + (u16)(data[1] & 0xFF)); + return ((((u16)data[0] & 0x0F) << 8) | (u16)(data[1] & 0xFF)) << 4; +} + +static u16 cxd2841er_read_agc_gain_i(struct cxd2841er_priv *priv, + u8 delsys) +{ + u8 data[2]; + + cxd2841er_write_reg( + priv, I2C_SLVT, 0x00, 0x60); + cxd2841er_read_regs(priv, I2C_SLVT, 0x26, data, 2); + + dev_dbg(&priv->i2c->dev, + "%s(): AGC value=%u\n", + __func__, (((u16)data[0] & 0x0F) << 8) | + (u16)(data[1] & 0xFF)); return ((((u16)data[0] & 0x0F) << 8) | (u16)(data[1] & 0xFF)) << 4; } @@ -1455,6 +1740,10 @@ static int cxd2841er_read_signal_strength(struct dvb_frontend *fe, *strength = 65535 - cxd2841er_read_agc_gain_t_t2( priv, p->delivery_system); break; + case SYS_ISDBT: + *strength = 65535 - cxd2841er_read_agc_gain_i( + priv, p->delivery_system); + break; case SYS_DVBS: case SYS_DVBS2: *strength = 65535 - cxd2841er_read_agc_gain_s(priv); @@ -1480,6 +1769,9 @@ static int cxd2841er_read_snr(struct dvb_frontend *fe, u16 *snr) case SYS_DVBT2: cxd2841er_read_snr_t2(priv, &tmp); break; + case SYS_ISDBT: + cxd2841er_read_snr_i(priv, &tmp); + break; case SYS_DVBS: case SYS_DVBS2: tmp = cxd2841er_dvbs_read_snr(priv, p->delivery_system); @@ -1506,6 +1798,9 @@ static int cxd2841er_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) case SYS_DVBT2: cxd2841er_read_packet_errors_t2(priv, ucblocks); break; + case SYS_ISDBT: + cxd2841er_read_packet_errors_i(priv, ucblocks); + break; default: *ucblocks = 0; break; @@ -1524,15 +1819,18 @@ static int cxd2841er_dvbt2_set_profile( switch (profile) { case DVBT2_PROFILE_BASE: tune_mode = 0x01; - seq_not2d_time = 12; + /* Set early unlock time */ + seq_not2d_time = (priv->xtal == SONY_XTAL_24000)?0x0E:0x0C; break; case DVBT2_PROFILE_LITE: tune_mode = 0x05; - seq_not2d_time = 40; + /* Set early unlock time */ + seq_not2d_time = (priv->xtal == SONY_XTAL_24000)?0x2E:0x28; break; case DVBT2_PROFILE_ANY: tune_mode = 0x00; - seq_not2d_time = 40; + /* Set early unlock time */ + seq_not2d_time = (priv->xtal == SONY_XTAL_24000)?0x2E:0x28; break; default: return -EINVAL; @@ -1574,254 +1872,617 @@ static int cxd2841er_sleep_tc_to_active_t2_band(struct cxd2841er_priv *priv, u32 bandwidth) { u32 iffreq; - u8 b20_9f[5]; - u8 b10_a6[14]; - u8 b10_b6[3]; - u8 b10_d7; + u8 data[MAX_WRITE_REGSIZE]; + + const uint8_t nominalRate8bw[3][5] = { + /* TRCG Nominal Rate [37:0] */ + {0x11, 0xF0, 0x00, 0x00, 0x00}, /* 20.5MHz XTal */ + {0x15, 0x00, 0x00, 0x00, 0x00}, /* 24MHz XTal */ + {0x11, 0xF0, 0x00, 0x00, 0x00} /* 41MHz XTal */ + }; + + const uint8_t nominalRate7bw[3][5] = { + /* TRCG Nominal Rate [37:0] */ + {0x14, 0x80, 0x00, 0x00, 0x00}, /* 20.5MHz XTal */ + {0x18, 0x00, 0x00, 0x00, 0x00}, /* 24MHz XTal */ + {0x14, 0x80, 0x00, 0x00, 0x00} /* 41MHz XTal */ + }; + + const uint8_t nominalRate6bw[3][5] = { + /* TRCG Nominal Rate [37:0] */ + {0x17, 0xEA, 0xAA, 0xAA, 0xAA}, /* 20.5MHz XTal */ + {0x1C, 0x00, 0x00, 0x00, 0x00}, /* 24MHz XTal */ + {0x17, 0xEA, 0xAA, 0xAA, 0xAA} /* 41MHz XTal */ + }; + + const uint8_t nominalRate5bw[3][5] = { + /* TRCG Nominal Rate [37:0] */ + {0x1C, 0xB3, 0x33, 0x33, 0x33}, /* 20.5MHz XTal */ + {0x21, 0x99, 0x99, 0x99, 0x99}, /* 24MHz XTal */ + {0x1C, 0xB3, 0x33, 0x33, 0x33} /* 41MHz XTal */ + }; + + const uint8_t nominalRate17bw[3][5] = { + /* TRCG Nominal Rate [37:0] */ + {0x58, 0xE2, 0xAF, 0xE0, 0xBC}, /* 20.5MHz XTal */ + {0x68, 0x0F, 0xA2, 0x32, 0xD0}, /* 24MHz XTal */ + {0x58, 0xE2, 0xAF, 0xE0, 0xBC} /* 41MHz XTal */ + }; + + const uint8_t itbCoef8bw[3][14] = { + {0x26, 0xAF, 0x06, 0xCD, 0x13, 0xBB, 0x28, 0xBA, + 0x23, 0xA9, 0x1F, 0xA8, 0x2C, 0xC8}, /* 20.5MHz XTal */ + {0x2F, 0xBA, 0x28, 0x9B, 0x28, 0x9D, 0x28, 0xA1, + 0x29, 0xA5, 0x2A, 0xAC, 0x29, 0xB5}, /* 24MHz XTal */ + {0x26, 0xAF, 0x06, 0xCD, 0x13, 0xBB, 0x28, 0xBA, + 0x23, 0xA9, 0x1F, 0xA8, 0x2C, 0xC8} /* 41MHz XTal */ + }; + + const uint8_t itbCoef7bw[3][14] = { + {0x2C, 0xBD, 0x02, 0xCF, 0x04, 0xF8, 0x23, 0xA6, + 0x29, 0xB0, 0x26, 0xA9, 0x21, 0xA5}, /* 20.5MHz XTal */ + {0x30, 0xB1, 0x29, 0x9A, 0x28, 0x9C, 0x28, 0xA0, + 0x29, 0xA2, 0x2B, 0xA6, 0x2B, 0xAD}, /* 24MHz XTal */ + {0x2C, 0xBD, 0x02, 0xCF, 0x04, 0xF8, 0x23, 0xA6, + 0x29, 0xB0, 0x26, 0xA9, 0x21, 0xA5} /* 41MHz XTal */ + }; + + const uint8_t itbCoef6bw[3][14] = { + {0x27, 0xA7, 0x28, 0xB3, 0x02, 0xF0, 0x01, 0xE8, + 0x00, 0xCF, 0x00, 0xE6, 0x23, 0xA4}, /* 20.5MHz XTal */ + {0x31, 0xA8, 0x29, 0x9B, 0x27, 0x9C, 0x28, 0x9E, + 0x29, 0xA4, 0x29, 0xA2, 0x29, 0xA8}, /* 24MHz XTal */ + {0x27, 0xA7, 0x28, 0xB3, 0x02, 0xF0, 0x01, 0xE8, + 0x00, 0xCF, 0x00, 0xE6, 0x23, 0xA4} /* 41MHz XTal */ + }; + + const uint8_t itbCoef5bw[3][14] = { + {0x27, 0xA7, 0x28, 0xB3, 0x02, 0xF0, 0x01, 0xE8, + 0x00, 0xCF, 0x00, 0xE6, 0x23, 0xA4}, /* 20.5MHz XTal */ + {0x31, 0xA8, 0x29, 0x9B, 0x27, 0x9C, 0x28, 0x9E, + 0x29, 0xA4, 0x29, 0xA2, 0x29, 0xA8}, /* 24MHz XTal */ + {0x27, 0xA7, 0x28, 0xB3, 0x02, 0xF0, 0x01, 0xE8, + 0x00, 0xCF, 0x00, 0xE6, 0x23, 0xA4} /* 41MHz XTal */ + }; + + const uint8_t itbCoef17bw[3][14] = { + {0x25, 0xA0, 0x36, 0x8D, 0x2E, 0x94, 0x28, 0x9B, + 0x32, 0x90, 0x2C, 0x9D, 0x29, 0x99}, /* 20.5MHz XTal */ + {0x33, 0x8E, 0x2B, 0x97, 0x2D, 0x95, 0x37, 0x8B, + 0x30, 0x97, 0x2D, 0x9A, 0x21, 0xA4}, /* 24MHz XTal */ + {0x25, 0xA0, 0x36, 0x8D, 0x2E, 0x94, 0x28, 0x9B, + 0x32, 0x90, 0x2C, 0x9D, 0x29, 0x99} /* 41MHz XTal */ + }; + + /* Set SLV-T Bank : 0x20 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x20); - dev_dbg(&priv->i2c->dev, "%s()\n", __func__); switch (bandwidth) { case 8000000: - /* bank 0x20, reg 0x9f */ - b20_9f[0] = 0x11; - b20_9f[1] = 0xf0; - b20_9f[2] = 0x00; - b20_9f[3] = 0x00; - b20_9f[4] = 0x00; - /* bank 0x10, reg 0xa6 */ - b10_a6[0] = 0x26; - b10_a6[1] = 0xaf; - b10_a6[2] = 0x06; - b10_a6[3] = 0xcd; - b10_a6[4] = 0x13; - b10_a6[5] = 0xbb; - b10_a6[6] = 0x28; - b10_a6[7] = 0xba; - b10_a6[8] = 0x23; - b10_a6[9] = 0xa9; - b10_a6[10] = 0x1f; - b10_a6[11] = 0xa8; - b10_a6[12] = 0x2c; - b10_a6[13] = 0xc8; - iffreq = MAKE_IFFREQ_CONFIG(4.80); - b10_d7 = 0x00; + /* <Timing Recovery setting> */ + cxd2841er_write_regs(priv, I2C_SLVT, + 0x9F, nominalRate8bw[priv->xtal], 5); + + /* Set SLV-T Bank : 0x27 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x27); + cxd2841er_set_reg_bits(priv, I2C_SLVT, + 0x7a, 0x00, 0x0f); + + /* Set SLV-T Bank : 0x10 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x10); + + /* Group delay equaliser settings for + * ASCOT2D, ASCOT2E and ASCOT3 tuners + */ + cxd2841er_write_regs(priv, I2C_SLVT, + 0xA6, itbCoef8bw[priv->xtal], 14); + /* <IF freq setting> */ + iffreq = MAKE_IFFREQ_CONFIG_XTAL(priv->xtal, 4.80); + data[0] = (u8) ((iffreq >> 16) & 0xff); + data[1] = (u8)((iffreq >> 8) & 0xff); + data[2] = (u8)(iffreq & 0xff); + cxd2841er_write_regs(priv, I2C_SLVT, 0xB6, data, 3); + /* System bandwidth setting */ + cxd2841er_set_reg_bits( + priv, I2C_SLVT, 0xD7, 0x00, 0x07); break; case 7000000: - /* bank 0x20, reg 0x9f */ - b20_9f[0] = 0x14; - b20_9f[1] = 0x80; - b20_9f[2] = 0x00; - b20_9f[3] = 0x00; - b20_9f[4] = 0x00; - /* bank 0x10, reg 0xa6 */ - b10_a6[0] = 0x2C; - b10_a6[1] = 0xBD; - b10_a6[2] = 0x02; - b10_a6[3] = 0xCF; - b10_a6[4] = 0x04; - b10_a6[5] = 0xF8; - b10_a6[6] = 0x23; - b10_a6[7] = 0xA6; - b10_a6[8] = 0x29; - b10_a6[9] = 0xB0; - b10_a6[10] = 0x26; - b10_a6[11] = 0xA9; - b10_a6[12] = 0x21; - b10_a6[13] = 0xA5; - iffreq = MAKE_IFFREQ_CONFIG(4.2); - b10_d7 = 0x02; + /* <Timing Recovery setting> */ + cxd2841er_write_regs(priv, I2C_SLVT, + 0x9F, nominalRate7bw[priv->xtal], 5); + + /* Set SLV-T Bank : 0x27 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x27); + cxd2841er_set_reg_bits(priv, I2C_SLVT, + 0x7a, 0x00, 0x0f); + + /* Set SLV-T Bank : 0x10 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x10); + + /* Group delay equaliser settings for + * ASCOT2D, ASCOT2E and ASCOT3 tuners + */ + cxd2841er_write_regs(priv, I2C_SLVT, + 0xA6, itbCoef7bw[priv->xtal], 14); + /* <IF freq setting> */ + iffreq = MAKE_IFFREQ_CONFIG_XTAL(priv->xtal, 4.20); + data[0] = (u8) ((iffreq >> 16) & 0xff); + data[1] = (u8)((iffreq >> 8) & 0xff); + data[2] = (u8)(iffreq & 0xff); + cxd2841er_write_regs(priv, I2C_SLVT, 0xB6, data, 3); + /* System bandwidth setting */ + cxd2841er_set_reg_bits( + priv, I2C_SLVT, 0xD7, 0x02, 0x07); break; case 6000000: - /* bank 0x20, reg 0x9f */ - b20_9f[0] = 0x17; - b20_9f[1] = 0xEA; - b20_9f[2] = 0xAA; - b20_9f[3] = 0xAA; - b20_9f[4] = 0xAA; - /* bank 0x10, reg 0xa6 */ - b10_a6[0] = 0x27; - b10_a6[1] = 0xA7; - b10_a6[2] = 0x28; - b10_a6[3] = 0xB3; - b10_a6[4] = 0x02; - b10_a6[5] = 0xF0; - b10_a6[6] = 0x01; - b10_a6[7] = 0xE8; - b10_a6[8] = 0x00; - b10_a6[9] = 0xCF; - b10_a6[10] = 0x00; - b10_a6[11] = 0xE6; - b10_a6[12] = 0x23; - b10_a6[13] = 0xA4; - iffreq = MAKE_IFFREQ_CONFIG(3.6); - b10_d7 = 0x04; + /* <Timing Recovery setting> */ + cxd2841er_write_regs(priv, I2C_SLVT, + 0x9F, nominalRate6bw[priv->xtal], 5); + + /* Set SLV-T Bank : 0x27 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x27); + cxd2841er_set_reg_bits(priv, I2C_SLVT, + 0x7a, 0x00, 0x0f); + + /* Set SLV-T Bank : 0x10 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x10); + + /* Group delay equaliser settings for + * ASCOT2D, ASCOT2E and ASCOT3 tuners + */ + cxd2841er_write_regs(priv, I2C_SLVT, + 0xA6, itbCoef6bw[priv->xtal], 14); + /* <IF freq setting> */ + iffreq = MAKE_IFFREQ_CONFIG_XTAL(priv->xtal, 3.60); + data[0] = (u8) ((iffreq >> 16) & 0xff); + data[1] = (u8)((iffreq >> 8) & 0xff); + data[2] = (u8)(iffreq & 0xff); + cxd2841er_write_regs(priv, I2C_SLVT, 0xB6, data, 3); + /* System bandwidth setting */ + cxd2841er_set_reg_bits( + priv, I2C_SLVT, 0xD7, 0x04, 0x07); break; case 5000000: - /* bank 0x20, reg 0x9f */ - b20_9f[0] = 0x1C; - b20_9f[1] = 0xB3; - b20_9f[2] = 0x33; - b20_9f[3] = 0x33; - b20_9f[4] = 0x33; - /* bank 0x10, reg 0xa6 */ - b10_a6[0] = 0x27; - b10_a6[1] = 0xA7; - b10_a6[2] = 0x28; - b10_a6[3] = 0xB3; - b10_a6[4] = 0x02; - b10_a6[5] = 0xF0; - b10_a6[6] = 0x01; - b10_a6[7] = 0xE8; - b10_a6[8] = 0x00; - b10_a6[9] = 0xCF; - b10_a6[10] = 0x00; - b10_a6[11] = 0xE6; - b10_a6[12] = 0x23; - b10_a6[13] = 0xA4; - iffreq = MAKE_IFFREQ_CONFIG(3.6); - b10_d7 = 0x06; + /* <Timing Recovery setting> */ + cxd2841er_write_regs(priv, I2C_SLVT, + 0x9F, nominalRate5bw[priv->xtal], 5); + + /* Set SLV-T Bank : 0x27 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x27); + cxd2841er_set_reg_bits(priv, I2C_SLVT, + 0x7a, 0x00, 0x0f); + + /* Set SLV-T Bank : 0x10 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x10); + + /* Group delay equaliser settings for + * ASCOT2D, ASCOT2E and ASCOT3 tuners + */ + cxd2841er_write_regs(priv, I2C_SLVT, + 0xA6, itbCoef5bw[priv->xtal], 14); + /* <IF freq setting> */ + iffreq = MAKE_IFFREQ_CONFIG_XTAL(priv->xtal, 3.60); + data[0] = (u8) ((iffreq >> 16) & 0xff); + data[1] = (u8)((iffreq >> 8) & 0xff); + data[2] = (u8)(iffreq & 0xff); + cxd2841er_write_regs(priv, I2C_SLVT, 0xB6, data, 3); + /* System bandwidth setting */ + cxd2841er_set_reg_bits( + priv, I2C_SLVT, 0xD7, 0x06, 0x07); break; case 1712000: - /* bank 0x20, reg 0x9f */ - b20_9f[0] = 0x58; - b20_9f[1] = 0xE2; - b20_9f[2] = 0xAF; - b20_9f[3] = 0xE0; - b20_9f[4] = 0xBC; - /* bank 0x10, reg 0xa6 */ - b10_a6[0] = 0x25; - b10_a6[1] = 0xA0; - b10_a6[2] = 0x36; - b10_a6[3] = 0x8D; - b10_a6[4] = 0x2E; - b10_a6[5] = 0x94; - b10_a6[6] = 0x28; - b10_a6[7] = 0x9B; - b10_a6[8] = 0x32; - b10_a6[9] = 0x90; - b10_a6[10] = 0x2C; - b10_a6[11] = 0x9D; - b10_a6[12] = 0x29; - b10_a6[13] = 0x99; - iffreq = MAKE_IFFREQ_CONFIG(3.5); - b10_d7 = 0x03; + /* <Timing Recovery setting> */ + cxd2841er_write_regs(priv, I2C_SLVT, + 0x9F, nominalRate17bw[priv->xtal], 5); + + /* Set SLV-T Bank : 0x27 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x27); + cxd2841er_set_reg_bits(priv, I2C_SLVT, + 0x7a, 0x03, 0x0f); + + /* Set SLV-T Bank : 0x10 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x10); + + /* Group delay equaliser settings for + * ASCOT2D, ASCOT2E and ASCOT3 tuners + */ + cxd2841er_write_regs(priv, I2C_SLVT, + 0xA6, itbCoef17bw[priv->xtal], 14); + /* <IF freq setting> */ + iffreq = MAKE_IFFREQ_CONFIG_XTAL(priv->xtal, 3.50); + data[0] = (u8) ((iffreq >> 16) & 0xff); + data[1] = (u8)((iffreq >> 8) & 0xff); + data[2] = (u8)(iffreq & 0xff); + cxd2841er_write_regs(priv, I2C_SLVT, 0xB6, data, 3); + /* System bandwidth setting */ + cxd2841er_set_reg_bits( + priv, I2C_SLVT, 0xD7, 0x03, 0x07); break; default: return -EINVAL; } - /* Set SLV-T Bank : 0x20 */ - cxd2841er_write_reg(priv, I2C_SLVX, 0x00, 0x20); - cxd2841er_write_regs(priv, I2C_SLVT, 0x9f, b20_9f, sizeof(b20_9f)); - /* Set SLV-T Bank : 0x27 */ - cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x27); - cxd2841er_set_reg_bits( - priv, I2C_SLVT, 0x7a, - (bandwidth == 1712000 ? 0x03 : 0x00), 0x0f); - /* Set SLV-T Bank : 0x10 */ - cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x10); - /* Group delay equaliser sett. for ASCOT2E */ - cxd2841er_write_regs(priv, I2C_SLVT, 0xa6, b10_a6, sizeof(b10_a6)); - /* <IF freq setting> */ - b10_b6[0] = (u8) ((iffreq >> 16) & 0xff); - b10_b6[1] = (u8)((iffreq >> 8) & 0xff); - b10_b6[2] = (u8)(iffreq & 0xff); - cxd2841er_write_regs(priv, I2C_SLVT, 0xb6, b10_b6, sizeof(b10_b6)); - /* System bandwidth setting */ - cxd2841er_set_reg_bits(priv, I2C_SLVT, 0xd7, b10_d7, 0x07); return 0; } static int cxd2841er_sleep_tc_to_active_t_band( struct cxd2841er_priv *priv, u32 bandwidth) { - u8 b13_9c[2] = { 0x01, 0x14 }; - u8 bw8mhz_b10_9f[] = { 0x11, 0xF0, 0x00, 0x00, 0x00 }; - u8 bw8mhz_b10_a6[] = { 0x26, 0xAF, 0x06, 0xCD, 0x13, 0xBB, - 0x28, 0xBA, 0x23, 0xA9, 0x1F, 0xA8, 0x2C, 0xC8 }; - u8 bw8mhz_b10_d9[] = { 0x01, 0xE0 }; - u8 bw8mhz_b17_38[] = { 0x01, 0x02 }; - u8 bw7mhz_b10_9f[] = { 0x14, 0x80, 0x00, 0x00, 0x00 }; - u8 bw7mhz_b10_a6[] = { 0x2C, 0xBD, 0x02, 0xCF, 0x04, 0xF8, - 0x23, 0xA6, 0x29, 0xB0, 0x26, 0xA9, 0x21, 0xA5 }; - u8 bw7mhz_b10_d9[] = { 0x12, 0xF8 }; - u8 bw7mhz_b17_38[] = { 0x00, 0x03 }; - u8 bw6mhz_b10_9f[] = { 0x17, 0xEA, 0xAA, 0xAA, 0xAA }; - u8 bw6mhz_b10_a6[] = { 0x27, 0xA7, 0x28, 0xB3, 0x02, 0xF0, - 0x01, 0xE8, 0x00, 0xCF, 0x00, 0xE6, 0x23, 0xA4 }; - u8 bw6mhz_b10_d9[] = { 0x1F, 0xDC }; - u8 bw6mhz_b17_38[] = { 0x00, 0x03 }; - u8 bw5mhz_b10_9f[] = { 0x1C, 0xB3, 0x33, 0x33, 0x33 }; - u8 bw5mhz_b10_a6[] = { 0x27, 0xA7, 0x28, 0xB3, 0x02, 0xF0, - 0x01, 0xE8, 0x00, 0xCF, 0x00, 0xE6, 0x23, 0xA4 }; - u8 bw5mhz_b10_d9[] = { 0x26, 0x3C }; - u8 bw5mhz_b17_38[] = { 0x00, 0x03 }; - u8 b10_b6[3]; - u8 d7val; + u8 data[MAX_WRITE_REGSIZE]; u32 iffreq; - u8 *b10_9f; - u8 *b10_a6; - u8 *b10_d9; - u8 *b17_38; + u8 nominalRate8bw[3][5] = { + /* TRCG Nominal Rate [37:0] */ + {0x11, 0xF0, 0x00, 0x00, 0x00}, /* 20.5MHz XTal */ + {0x15, 0x00, 0x00, 0x00, 0x00}, /* 24MHz XTal */ + {0x11, 0xF0, 0x00, 0x00, 0x00} /* 41MHz XTal */ + }; + u8 nominalRate7bw[3][5] = { + /* TRCG Nominal Rate [37:0] */ + {0x14, 0x80, 0x00, 0x00, 0x00}, /* 20.5MHz XTal */ + {0x18, 0x00, 0x00, 0x00, 0x00}, /* 24MHz XTal */ + {0x14, 0x80, 0x00, 0x00, 0x00} /* 41MHz XTal */ + }; + u8 nominalRate6bw[3][5] = { + /* TRCG Nominal Rate [37:0] */ + {0x17, 0xEA, 0xAA, 0xAA, 0xAA}, /* 20.5MHz XTal */ + {0x1C, 0x00, 0x00, 0x00, 0x00}, /* 24MHz XTal */ + {0x17, 0xEA, 0xAA, 0xAA, 0xAA} /* 41MHz XTal */ + }; + u8 nominalRate5bw[3][5] = { + /* TRCG Nominal Rate [37:0] */ + {0x1C, 0xB3, 0x33, 0x33, 0x33}, /* 20.5MHz XTal */ + {0x21, 0x99, 0x99, 0x99, 0x99}, /* 24MHz XTal */ + {0x1C, 0xB3, 0x33, 0x33, 0x33} /* 41MHz XTal */ + }; - dev_dbg(&priv->i2c->dev, "%s()\n", __func__); + u8 itbCoef8bw[3][14] = { + {0x26, 0xAF, 0x06, 0xCD, 0x13, 0xBB, 0x28, 0xBA, 0x23, 0xA9, + 0x1F, 0xA8, 0x2C, 0xC8}, /* 20.5MHz XTal */ + {0x2F, 0xBA, 0x28, 0x9B, 0x28, 0x9D, 0x28, 0xA1, 0x29, 0xA5, + 0x2A, 0xAC, 0x29, 0xB5}, /* 24MHz XTal */ + {0x26, 0xAF, 0x06, 0xCD, 0x13, 0xBB, 0x28, 0xBA, 0x23, 0xA9, + 0x1F, 0xA8, 0x2C, 0xC8} /* 41MHz XTal */ + }; + u8 itbCoef7bw[3][14] = { + {0x2C, 0xBD, 0x02, 0xCF, 0x04, 0xF8, 0x23, 0xA6, 0x29, 0xB0, + 0x26, 0xA9, 0x21, 0xA5}, /* 20.5MHz XTal */ + {0x30, 0xB1, 0x29, 0x9A, 0x28, 0x9C, 0x28, 0xA0, 0x29, 0xA2, + 0x2B, 0xA6, 0x2B, 0xAD}, /* 24MHz XTal */ + {0x2C, 0xBD, 0x02, 0xCF, 0x04, 0xF8, 0x23, 0xA6, 0x29, 0xB0, + 0x26, 0xA9, 0x21, 0xA5} /* 41MHz XTal */ + }; + u8 itbCoef6bw[3][14] = { + {0x27, 0xA7, 0x28, 0xB3, 0x02, 0xF0, 0x01, 0xE8, 0x00, 0xCF, + 0x00, 0xE6, 0x23, 0xA4}, /* 20.5MHz XTal */ + {0x31, 0xA8, 0x29, 0x9B, 0x27, 0x9C, 0x28, 0x9E, 0x29, 0xA4, + 0x29, 0xA2, 0x29, 0xA8}, /* 24MHz XTal */ + {0x27, 0xA7, 0x28, 0xB3, 0x02, 0xF0, 0x01, 0xE8, 0x00, 0xCF, + 0x00, 0xE6, 0x23, 0xA4} /* 41MHz XTal */ + }; + u8 itbCoef5bw[3][14] = { + {0x27, 0xA7, 0x28, 0xB3, 0x02, 0xF0, 0x01, 0xE8, 0x00, 0xCF, + 0x00, 0xE6, 0x23, 0xA4}, /* 20.5MHz XTal */ + {0x31, 0xA8, 0x29, 0x9B, 0x27, 0x9C, 0x28, 0x9E, 0x29, 0xA4, + 0x29, 0xA2, 0x29, 0xA8}, /* 24MHz XTal */ + {0x27, 0xA7, 0x28, 0xB3, 0x02, 0xF0, 0x01, 0xE8, 0x00, 0xCF, + 0x00, 0xE6, 0x23, 0xA4} /* 41MHz XTal */ + }; + + /* Set SLV-T Bank : 0x13 */ cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x13); /* Echo performance optimization setting */ - cxd2841er_write_regs(priv, I2C_SLVT, 0x9c, b13_9c, sizeof(b13_9c)); + data[0] = 0x01; + data[1] = 0x14; + cxd2841er_write_regs(priv, I2C_SLVT, 0x9C, data, 2); + + /* Set SLV-T Bank : 0x10 */ cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x10); switch (bandwidth) { case 8000000: - b10_9f = bw8mhz_b10_9f; - b10_a6 = bw8mhz_b10_a6; - b10_d9 = bw8mhz_b10_d9; - b17_38 = bw8mhz_b17_38; - d7val = 0; - iffreq = MAKE_IFFREQ_CONFIG(4.80); + /* <Timing Recovery setting> */ + cxd2841er_write_regs(priv, I2C_SLVT, + 0x9F, nominalRate8bw[priv->xtal], 5); + /* Group delay equaliser settings for + * ASCOT2D, ASCOT2E and ASCOT3 tuners + */ + cxd2841er_write_regs(priv, I2C_SLVT, + 0xA6, itbCoef8bw[priv->xtal], 14); + /* <IF freq setting> */ + iffreq = MAKE_IFFREQ_CONFIG_XTAL(priv->xtal, 4.80); + data[0] = (u8) ((iffreq >> 16) & 0xff); + data[1] = (u8)((iffreq >> 8) & 0xff); + data[2] = (u8)(iffreq & 0xff); + cxd2841er_write_regs(priv, I2C_SLVT, 0xB6, data, 3); + /* System bandwidth setting */ + cxd2841er_set_reg_bits( + priv, I2C_SLVT, 0xD7, 0x00, 0x07); + + /* Demod core latency setting */ + if (priv->xtal == SONY_XTAL_24000) { + data[0] = 0x15; + data[1] = 0x28; + } else { + data[0] = 0x01; + data[1] = 0xE0; + } + cxd2841er_write_regs(priv, I2C_SLVT, 0xD9, data, 2); + + /* Notch filter setting */ + data[0] = 0x01; + data[1] = 0x02; + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x17); + cxd2841er_write_regs(priv, I2C_SLVT, 0x38, data, 2); break; case 7000000: - b10_9f = bw7mhz_b10_9f; - b10_a6 = bw7mhz_b10_a6; - b10_d9 = bw7mhz_b10_d9; - b17_38 = bw7mhz_b17_38; - d7val = 2; - iffreq = MAKE_IFFREQ_CONFIG(4.20); + /* <Timing Recovery setting> */ + cxd2841er_write_regs(priv, I2C_SLVT, + 0x9F, nominalRate7bw[priv->xtal], 5); + /* Group delay equaliser settings for + * ASCOT2D, ASCOT2E and ASCOT3 tuners + */ + cxd2841er_write_regs(priv, I2C_SLVT, + 0xA6, itbCoef7bw[priv->xtal], 14); + /* <IF freq setting> */ + iffreq = MAKE_IFFREQ_CONFIG_XTAL(priv->xtal, 4.20); + data[0] = (u8) ((iffreq >> 16) & 0xff); + data[1] = (u8)((iffreq >> 8) & 0xff); + data[2] = (u8)(iffreq & 0xff); + cxd2841er_write_regs(priv, I2C_SLVT, 0xB6, data, 3); + /* System bandwidth setting */ + cxd2841er_set_reg_bits( + priv, I2C_SLVT, 0xD7, 0x02, 0x07); + + /* Demod core latency setting */ + if (priv->xtal == SONY_XTAL_24000) { + data[0] = 0x1F; + data[1] = 0xF8; + } else { + data[0] = 0x12; + data[1] = 0xF8; + } + cxd2841er_write_regs(priv, I2C_SLVT, 0xD9, data, 2); + + /* Notch filter setting */ + data[0] = 0x00; + data[1] = 0x03; + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x17); + cxd2841er_write_regs(priv, I2C_SLVT, 0x38, data, 2); break; case 6000000: - b10_9f = bw6mhz_b10_9f; - b10_a6 = bw6mhz_b10_a6; - b10_d9 = bw6mhz_b10_d9; - b17_38 = bw6mhz_b17_38; - d7val = 4; - iffreq = MAKE_IFFREQ_CONFIG(3.60); + /* <Timing Recovery setting> */ + cxd2841er_write_regs(priv, I2C_SLVT, + 0x9F, nominalRate6bw[priv->xtal], 5); + /* Group delay equaliser settings for + * ASCOT2D, ASCOT2E and ASCOT3 tuners + */ + cxd2841er_write_regs(priv, I2C_SLVT, + 0xA6, itbCoef6bw[priv->xtal], 14); + /* <IF freq setting> */ + iffreq = MAKE_IFFREQ_CONFIG_XTAL(priv->xtal, 3.60); + data[0] = (u8) ((iffreq >> 16) & 0xff); + data[1] = (u8)((iffreq >> 8) & 0xff); + data[2] = (u8)(iffreq & 0xff); + cxd2841er_write_regs(priv, I2C_SLVT, 0xB6, data, 3); + /* System bandwidth setting */ + cxd2841er_set_reg_bits( + priv, I2C_SLVT, 0xD7, 0x04, 0x07); + + /* Demod core latency setting */ + if (priv->xtal == SONY_XTAL_24000) { + data[0] = 0x25; + data[1] = 0x4C; + } else { + data[0] = 0x1F; + data[1] = 0xDC; + } + cxd2841er_write_regs(priv, I2C_SLVT, 0xD9, data, 2); + + /* Notch filter setting */ + data[0] = 0x00; + data[1] = 0x03; + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x17); + cxd2841er_write_regs(priv, I2C_SLVT, 0x38, data, 2); break; case 5000000: - b10_9f = bw5mhz_b10_9f; - b10_a6 = bw5mhz_b10_a6; - b10_d9 = bw5mhz_b10_d9; - b17_38 = bw5mhz_b17_38; - d7val = 6; - iffreq = MAKE_IFFREQ_CONFIG(3.60); + /* <Timing Recovery setting> */ + cxd2841er_write_regs(priv, I2C_SLVT, + 0x9F, nominalRate5bw[priv->xtal], 5); + /* Group delay equaliser settings for + * ASCOT2D, ASCOT2E and ASCOT3 tuners + */ + cxd2841er_write_regs(priv, I2C_SLVT, + 0xA6, itbCoef5bw[priv->xtal], 14); + /* <IF freq setting> */ + iffreq = MAKE_IFFREQ_CONFIG_XTAL(priv->xtal, 3.60); + data[0] = (u8) ((iffreq >> 16) & 0xff); + data[1] = (u8)((iffreq >> 8) & 0xff); + data[2] = (u8)(iffreq & 0xff); + cxd2841er_write_regs(priv, I2C_SLVT, 0xB6, data, 3); + /* System bandwidth setting */ + cxd2841er_set_reg_bits( + priv, I2C_SLVT, 0xD7, 0x06, 0x07); + + /* Demod core latency setting */ + if (priv->xtal == SONY_XTAL_24000) { + data[0] = 0x2C; + data[1] = 0xC2; + } else { + data[0] = 0x26; + data[1] = 0x3C; + } + cxd2841er_write_regs(priv, I2C_SLVT, 0xD9, data, 2); + + /* Notch filter setting */ + data[0] = 0x00; + data[1] = 0x03; + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x17); + cxd2841er_write_regs(priv, I2C_SLVT, 0x38, data, 2); + break; + } + + return 0; +} + +static int cxd2841er_sleep_tc_to_active_i_band( + struct cxd2841er_priv *priv, u32 bandwidth) +{ + u32 iffreq; + u8 data[3]; + + /* TRCG Nominal Rate */ + u8 nominalRate8bw[3][5] = { + {0x00, 0x00, 0x00, 0x00, 0x00}, /* 20.5MHz XTal */ + {0x11, 0xB8, 0x00, 0x00, 0x00}, /* 24MHz XTal */ + {0x00, 0x00, 0x00, 0x00, 0x00} /* 41MHz XTal */ + }; + + u8 nominalRate7bw[3][5] = { + {0x00, 0x00, 0x00, 0x00, 0x00}, /* 20.5MHz XTal */ + {0x14, 0x40, 0x00, 0x00, 0x00}, /* 24MHz XTal */ + {0x00, 0x00, 0x00, 0x00, 0x00} /* 41MHz XTal */ + }; + + u8 nominalRate6bw[3][5] = { + {0x14, 0x2E, 0x00, 0x00, 0x00}, /* 20.5MHz XTal */ + {0x17, 0xA0, 0x00, 0x00, 0x00}, /* 24MHz XTal */ + {0x14, 0x2E, 0x00, 0x00, 0x00} /* 41MHz XTal */ + }; + + u8 itbCoef8bw[3][14] = { + {0x00}, /* 20.5MHz XTal */ + {0x2F, 0xBA, 0x28, 0x9B, 0x28, 0x9D, 0x28, 0xA1, 0x29, + 0xA5, 0x2A, 0xAC, 0x29, 0xB5}, /* 24MHz Xtal */ + {0x0}, /* 41MHz XTal */ + }; + + u8 itbCoef7bw[3][14] = { + {0x00}, /* 20.5MHz XTal */ + {0x30, 0xB1, 0x29, 0x9A, 0x28, 0x9C, 0x28, 0xA0, 0x29, + 0xA2, 0x2B, 0xA6, 0x2B, 0xAD}, /* 24MHz Xtal */ + {0x00}, /* 41MHz XTal */ + }; + + u8 itbCoef6bw[3][14] = { + {0x27, 0xA7, 0x28, 0xB3, 0x02, 0xF0, 0x01, 0xE8, 0x00, + 0xCF, 0x00, 0xE6, 0x23, 0xA4}, /* 20.5MHz XTal */ + {0x31, 0xA8, 0x29, 0x9B, 0x27, 0x9C, 0x28, 0x9E, 0x29, + 0xA4, 0x29, 0xA2, 0x29, 0xA8}, /* 24MHz Xtal */ + {0x27, 0xA7, 0x28, 0xB3, 0x02, 0xF0, 0x01, 0xE8, 0x00, + 0xCF, 0x00, 0xE6, 0x23, 0xA4}, /* 41MHz XTal */ + }; + + dev_dbg(&priv->i2c->dev, "%s() bandwidth=%u\n", __func__, bandwidth); + /* Set SLV-T Bank : 0x10 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x10); + + /* 20.5/41MHz Xtal support is not available + * on ISDB-T 7MHzBW and 8MHzBW + */ + if (priv->xtal != SONY_XTAL_24000 && bandwidth > 6000000) { + dev_err(&priv->i2c->dev, + "%s(): bandwidth %d supported only for 24MHz xtal\n", + __func__, bandwidth); + return -EINVAL; + } + + switch (bandwidth) { + case 8000000: + /* TRCG Nominal Rate */ + cxd2841er_write_regs(priv, I2C_SLVT, + 0x9F, nominalRate8bw[priv->xtal], 5); + /* Group delay equaliser settings for ASCOT tuners optimized */ + cxd2841er_write_regs(priv, I2C_SLVT, + 0xA6, itbCoef8bw[priv->xtal], 14); + + /* IF freq setting */ + iffreq = MAKE_IFFREQ_CONFIG_XTAL(priv->xtal, 4.75); + data[0] = (u8) ((iffreq >> 16) & 0xff); + data[1] = (u8)((iffreq >> 8) & 0xff); + data[2] = (u8)(iffreq & 0xff); + cxd2841er_write_regs(priv, I2C_SLVT, 0xB6, data, 3); + + /* System bandwidth setting */ + cxd2841er_set_reg_bits(priv, I2C_SLVT, 0xd7, 0x0, 0x7); + + /* Demod core latency setting */ + data[0] = 0x13; + data[1] = 0xFC; + cxd2841er_write_regs(priv, I2C_SLVT, 0xD9, data, 2); + + /* Acquisition optimization setting */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x12); + cxd2841er_set_reg_bits(priv, I2C_SLVT, 0x71, 0x03, 0x07); + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x15); + cxd2841er_write_reg(priv, I2C_SLVT, 0xBE, 0x03); + break; + case 7000000: + /* TRCG Nominal Rate */ + cxd2841er_write_regs(priv, I2C_SLVT, + 0x9F, nominalRate7bw[priv->xtal], 5); + /* Group delay equaliser settings for ASCOT tuners optimized */ + cxd2841er_write_regs(priv, I2C_SLVT, + 0xA6, itbCoef7bw[priv->xtal], 14); + + /* IF freq setting */ + iffreq = MAKE_IFFREQ_CONFIG_XTAL(priv->xtal, 4.15); + data[0] = (u8) ((iffreq >> 16) & 0xff); + data[1] = (u8)((iffreq >> 8) & 0xff); + data[2] = (u8)(iffreq & 0xff); + cxd2841er_write_regs(priv, I2C_SLVT, 0xB6, data, 3); + + /* System bandwidth setting */ + cxd2841er_set_reg_bits(priv, I2C_SLVT, 0xd7, 0x02, 0x7); + + /* Demod core latency setting */ + data[0] = 0x1A; + data[1] = 0xFA; + cxd2841er_write_regs(priv, I2C_SLVT, 0xD9, data, 2); + + /* Acquisition optimization setting */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x12); + cxd2841er_set_reg_bits(priv, I2C_SLVT, 0x71, 0x03, 0x07); + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x15); + cxd2841er_write_reg(priv, I2C_SLVT, 0xBE, 0x02); + break; + case 6000000: + /* TRCG Nominal Rate */ + cxd2841er_write_regs(priv, I2C_SLVT, + 0x9F, nominalRate6bw[priv->xtal], 5); + /* Group delay equaliser settings for ASCOT tuners optimized */ + cxd2841er_write_regs(priv, I2C_SLVT, + 0xA6, itbCoef6bw[priv->xtal], 14); + + /* IF freq setting */ + iffreq = MAKE_IFFREQ_CONFIG_XTAL(priv->xtal, 3.55); + data[0] = (u8) ((iffreq >> 16) & 0xff); + data[1] = (u8)((iffreq >> 8) & 0xff); + data[2] = (u8)(iffreq & 0xff); + cxd2841er_write_regs(priv, I2C_SLVT, 0xB6, data, 3); + + /* System bandwidth setting */ + cxd2841er_set_reg_bits(priv, I2C_SLVT, 0xd7, 0x04, 0x7); + + /* Demod core latency setting */ + if (priv->xtal == SONY_XTAL_24000) { + data[0] = 0x1F; + data[1] = 0x79; + } else { + data[0] = 0x1A; + data[1] = 0xE2; + } + cxd2841er_write_regs(priv, I2C_SLVT, 0xD9, data, 2); + + /* Acquisition optimization setting */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x12); + cxd2841er_set_reg_bits(priv, I2C_SLVT, 0x71, 0x07, 0x07); + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x15); + cxd2841er_write_reg(priv, I2C_SLVT, 0xBE, 0x02); break; default: dev_dbg(&priv->i2c->dev, "%s(): invalid bandwidth %d\n", - __func__, bandwidth); + __func__, bandwidth); return -EINVAL; } - /* <IF freq setting> */ - b10_b6[0] = (u8) ((iffreq >> 16) & 0xff); - b10_b6[1] = (u8)((iffreq >> 8) & 0xff); - b10_b6[2] = (u8)(iffreq & 0xff); - cxd2841er_write_regs( - priv, I2C_SLVT, 0x9f, b10_9f, sizeof(bw8mhz_b10_9f)); - cxd2841er_write_regs( - priv, I2C_SLVT, 0xa6, b10_a6, sizeof(bw8mhz_b10_a6)); - cxd2841er_write_regs(priv, I2C_SLVT, 0xb6, b10_b6, sizeof(b10_b6)); - cxd2841er_set_reg_bits(priv, I2C_SLVT, 0xd7, d7val, 0x7); - cxd2841er_write_regs( - priv, I2C_SLVT, 0xd9, b10_d9, sizeof(bw8mhz_b10_d9)); - cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x17); - cxd2841er_write_regs( - priv, I2C_SLVT, 0x38, b17_38, sizeof(bw8mhz_b17_38)); return 0; } @@ -1837,7 +2498,7 @@ static int cxd2841er_sleep_tc_to_active_c_band(struct cxd2841er_priv *priv, u8 b10_b6[3]; u32 iffreq; - dev_dbg(&priv->i2c->dev, "%s()\n", __func__); + dev_dbg(&priv->i2c->dev, "%s() bw=%d\n", __func__, bandwidth); cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x10); switch (bandwidth) { case 8000000: @@ -1854,7 +2515,7 @@ static int cxd2841er_sleep_tc_to_active_c_band(struct cxd2841er_priv *priv, iffreq = MAKE_IFFREQ_CONFIG(3.7); break; default: - dev_dbg(&priv->i2c->dev, "%s(): unsupported bandwidth %d\n", + dev_err(&priv->i2c->dev, "%s(): unsupported bandwidth %d\n", __func__, bandwidth); return -EINVAL; } @@ -1902,6 +2563,7 @@ static int cxd2841er_sleep_tc_to_active_t(struct cxd2841er_priv *priv, u32 bandwidth) { u8 data[2] = { 0x09, 0x54 }; + u8 data24m[3] = {0xDC, 0x6C, 0x00}; dev_dbg(&priv->i2c->dev, "%s()\n", __func__); cxd2841er_set_ts_clock_mode(priv, SYS_DVBT); @@ -1919,7 +2581,11 @@ static int cxd2841er_sleep_tc_to_active_t(struct cxd2841er_priv *priv, cxd2841er_write_reg(priv, I2C_SLVT, 0x30, 0x00); /* Enable ADC 1 */ cxd2841er_write_reg(priv, I2C_SLVT, 0x41, 0x1a); - /* xtal freq 20.5MHz */ + /* Enable ADC 2 & 3 */ + if (priv->xtal == SONY_XTAL_41000) { + data[0] = 0x0A; + data[1] = 0xD4; + } cxd2841er_write_regs(priv, I2C_SLVT, 0x43, data, 2); /* Enable ADC 4 */ cxd2841er_write_reg(priv, I2C_SLVX, 0x18, 0x00); @@ -1947,6 +2613,15 @@ static int cxd2841er_sleep_tc_to_active_t(struct cxd2841er_priv *priv, /* TSIF setting */ cxd2841er_set_reg_bits(priv, I2C_SLVT, 0xce, 0x01, 0x01); cxd2841er_set_reg_bits(priv, I2C_SLVT, 0xcf, 0x01, 0x01); + + if (priv->xtal == SONY_XTAL_24000) { + /* Set SLV-T Bank : 0x10 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x10); + cxd2841er_write_reg(priv, I2C_SLVT, 0xBF, 0x60); + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x18); + cxd2841er_write_regs(priv, I2C_SLVT, 0x24, data24m, 3); + } + cxd2841er_sleep_tc_to_active_t_band(priv, bandwidth); /* Set SLV-T Bank : 0x00 */ cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x00); @@ -1961,7 +2636,7 @@ static int cxd2841er_sleep_tc_to_active_t(struct cxd2841er_priv *priv, static int cxd2841er_sleep_tc_to_active_t2(struct cxd2841er_priv *priv, u32 bandwidth) { - u8 data[2] = { 0x09, 0x54 }; + u8 data[MAX_WRITE_REGSIZE]; dev_dbg(&priv->i2c->dev, "%s()\n", __func__); cxd2841er_set_ts_clock_mode(priv, SYS_DVBT2); @@ -1974,12 +2649,21 @@ static int cxd2841er_sleep_tc_to_active_t2(struct cxd2841er_priv *priv, /* Enable demod clock */ cxd2841er_write_reg(priv, I2C_SLVT, 0x2c, 0x01); /* Disable RF level monitor */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x59, 0x00); cxd2841er_write_reg(priv, I2C_SLVT, 0x2f, 0x00); /* Enable ADC clock */ cxd2841er_write_reg(priv, I2C_SLVT, 0x30, 0x00); /* Enable ADC 1 */ cxd2841er_write_reg(priv, I2C_SLVT, 0x41, 0x1a); - /* xtal freq 20.5MHz */ + + if (priv->xtal == SONY_XTAL_41000) { + data[0] = 0x0A; + data[1] = 0xD4; + } else { + data[0] = 0x09; + data[1] = 0x54; + } + cxd2841er_write_regs(priv, I2C_SLVT, 0x43, data, 2); /* Enable ADC 4 */ cxd2841er_write_reg(priv, I2C_SLVX, 0x18, 0x00); @@ -2002,6 +2686,10 @@ static int cxd2841er_sleep_tc_to_active_t2(struct cxd2841er_priv *priv, /* Set SLV-T Bank : 0x2b */ cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x2b); cxd2841er_set_reg_bits(priv, I2C_SLVT, 0x76, 0x20, 0x70); + /* Set SLV-T Bank : 0x23 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x23); + /* L1 Control setting */ + cxd2841er_set_reg_bits(priv, I2C_SLVT, 0xE6, 0x00, 0x03); /* Set SLV-T Bank : 0x00 */ cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x00); /* TSIF setting */ @@ -2020,6 +2708,72 @@ static int cxd2841er_sleep_tc_to_active_t2(struct cxd2841er_priv *priv, cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x2b); cxd2841er_set_reg_bits(priv, I2C_SLVT, 0x11, 0x20, 0x3f); + /* 24MHz Xtal setting */ + if (priv->xtal == SONY_XTAL_24000) { + /* Set SLV-T Bank : 0x11 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x11); + data[0] = 0xEB; + data[1] = 0x03; + data[2] = 0x3B; + cxd2841er_write_regs(priv, I2C_SLVT, 0x33, data, 3); + + /* Set SLV-T Bank : 0x20 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x20); + data[0] = 0x5E; + data[1] = 0x5E; + data[2] = 0x47; + cxd2841er_write_regs(priv, I2C_SLVT, 0x95, data, 3); + + cxd2841er_write_reg(priv, I2C_SLVT, 0x99, 0x18); + + data[0] = 0x3F; + data[1] = 0xFF; + cxd2841er_write_regs(priv, I2C_SLVT, 0xD9, data, 2); + + /* Set SLV-T Bank : 0x24 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x24); + data[0] = 0x0B; + data[1] = 0x72; + cxd2841er_write_regs(priv, I2C_SLVT, 0x34, data, 2); + + data[0] = 0x93; + data[1] = 0xF3; + data[2] = 0x00; + cxd2841er_write_regs(priv, I2C_SLVT, 0xD2, data, 3); + + data[0] = 0x05; + data[1] = 0xB8; + data[2] = 0xD8; + cxd2841er_write_regs(priv, I2C_SLVT, 0xDD, data, 3); + + cxd2841er_write_reg(priv, I2C_SLVT, 0xE0, 0x00); + + /* Set SLV-T Bank : 0x25 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x25); + cxd2841er_write_reg(priv, I2C_SLVT, 0xED, 0x60); + + /* Set SLV-T Bank : 0x27 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x27); + cxd2841er_write_reg(priv, I2C_SLVT, 0xFA, 0x34); + + /* Set SLV-T Bank : 0x2B */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x2B); + cxd2841er_write_reg(priv, I2C_SLVT, 0x4B, 0x2F); + cxd2841er_write_reg(priv, I2C_SLVT, 0x9E, 0x0E); + + /* Set SLV-T Bank : 0x2D */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x2D); + data[0] = 0x89; + data[1] = 0x89; + cxd2841er_write_regs(priv, I2C_SLVT, 0x24, data, 2); + + /* Set SLV-T Bank : 0x5E */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x5E); + data[0] = 0x24; + data[1] = 0x95; + cxd2841er_write_regs(priv, I2C_SLVT, 0x8C, data, 2); + } + cxd2841er_sleep_tc_to_active_t2_band(priv, bandwidth); /* Set SLV-T Bank : 0x00 */ @@ -2032,6 +2786,84 @@ static int cxd2841er_sleep_tc_to_active_t2(struct cxd2841er_priv *priv, return 0; } +/* ISDB-Tb part */ +static int cxd2841er_sleep_tc_to_active_i(struct cxd2841er_priv *priv, + u32 bandwidth) +{ + u8 data[2] = { 0x09, 0x54 }; + u8 data24m[2] = {0x60, 0x00}; + u8 data24m2[3] = {0xB7, 0x1B, 0x00}; + + dev_dbg(&priv->i2c->dev, "%s()\n", __func__); + cxd2841er_set_ts_clock_mode(priv, SYS_DVBT); + /* Set SLV-X Bank : 0x00 */ + cxd2841er_write_reg(priv, I2C_SLVX, 0x00, 0x00); + /* Set demod mode */ + cxd2841er_write_reg(priv, I2C_SLVX, 0x17, 0x06); + /* Set SLV-T Bank : 0x00 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x00); + /* Enable demod clock */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x2c, 0x01); + /* Enable RF level monitor */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x2f, 0x01); + cxd2841er_write_reg(priv, I2C_SLVT, 0x59, 0x01); + /* Enable ADC clock */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x30, 0x00); + /* Enable ADC 1 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x41, 0x1a); + /* xtal freq 20.5MHz or 24M */ + cxd2841er_write_regs(priv, I2C_SLVT, 0x43, data, 2); + /* Enable ADC 4 */ + cxd2841er_write_reg(priv, I2C_SLVX, 0x18, 0x00); + /* ASCOT setting ON */ + cxd2841er_set_reg_bits(priv, I2C_SLVT, 0xa5, 0x01, 0x01); + /* FEC Auto Recovery setting */ + cxd2841er_set_reg_bits(priv, I2C_SLVT, 0x30, 0x01, 0x01); + cxd2841er_set_reg_bits(priv, I2C_SLVT, 0x31, 0x00, 0x01); + /* ISDB-T initial setting */ + /* Set SLV-T Bank : 0x00 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x00); + cxd2841er_set_reg_bits(priv, I2C_SLVT, 0xce, 0x00, 0x01); + cxd2841er_set_reg_bits(priv, I2C_SLVT, 0xcf, 0x00, 0x01); + /* Set SLV-T Bank : 0x10 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x10); + cxd2841er_set_reg_bits(priv, I2C_SLVT, 0x69, 0x04, 0x07); + cxd2841er_set_reg_bits(priv, I2C_SLVT, 0x6B, 0x03, 0x07); + cxd2841er_set_reg_bits(priv, I2C_SLVT, 0x9D, 0x50, 0xFF); + cxd2841er_set_reg_bits(priv, I2C_SLVT, 0xD3, 0x06, 0x1F); + cxd2841er_set_reg_bits(priv, I2C_SLVT, 0xED, 0x00, 0x01); + cxd2841er_set_reg_bits(priv, I2C_SLVT, 0xE2, 0xCE, 0x80); + cxd2841er_set_reg_bits(priv, I2C_SLVT, 0xF2, 0x13, 0x10); + cxd2841er_set_reg_bits(priv, I2C_SLVT, 0xDE, 0x2E, 0x3F); + /* Set SLV-T Bank : 0x15 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x15); + cxd2841er_set_reg_bits(priv, I2C_SLVT, 0xDE, 0x02, 0x03); + /* Set SLV-T Bank : 0x1E */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x1E); + cxd2841er_set_reg_bits(priv, I2C_SLVT, 0x73, 0x68, 0xFF); + /* Set SLV-T Bank : 0x63 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x63); + cxd2841er_set_reg_bits(priv, I2C_SLVT, 0x81, 0x00, 0x01); + + /* for xtal 24MHz */ + /* Set SLV-T Bank : 0x10 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x10); + cxd2841er_write_regs(priv, I2C_SLVT, 0xBF, data24m, 2); + /* Set SLV-T Bank : 0x60 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x60); + cxd2841er_write_regs(priv, I2C_SLVT, 0xA8, data24m2, 3); + + cxd2841er_sleep_tc_to_active_i_band(priv, bandwidth); + /* Set SLV-T Bank : 0x00 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x00); + /* Disable HiZ Setting 1 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x80, 0x28); + /* Disable HiZ Setting 2 */ + cxd2841er_write_reg(priv, I2C_SLVT, 0x81, 0x00); + priv->state = STATE_ACTIVE_TC; + return 0; +} + static int cxd2841er_sleep_tc_to_active_c(struct cxd2841er_priv *priv, u32 bandwidth) { @@ -2079,7 +2911,7 @@ static int cxd2841er_sleep_tc_to_active_c(struct cxd2841er_priv *priv, cxd2841er_set_reg_bits(priv, I2C_SLVT, 0xce, 0x01, 0x01); cxd2841er_set_reg_bits(priv, I2C_SLVT, 0xcf, 0x01, 0x01); - cxd2841er_sleep_tc_to_active_c_band(priv, 8000000); + cxd2841er_sleep_tc_to_active_c_band(priv, bandwidth); /* Set SLV-T Bank : 0x00 */ cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x00); /* Disable HiZ Setting 1 */ @@ -2142,10 +2974,10 @@ static int cxd2841er_set_frontend_s(struct dvb_frontend *fe) struct dtv_frontend_properties *p = &fe->dtv_property_cache; u32 symbol_rate = p->symbol_rate/1000; - dev_dbg(&priv->i2c->dev, "%s(): %s frequency=%d symbol_rate=%d\n", + dev_dbg(&priv->i2c->dev, "%s(): %s frequency=%d symbol_rate=%d xtal=%d\n", __func__, (p->delivery_system == SYS_DVBS ? "DVB-S" : "DVB-S2"), - p->frequency, symbol_rate); + p->frequency, symbol_rate, priv->xtal); switch (priv->state) { case STATE_SLEEP_S: ret = cxd2841er_sleep_s_to_active_s( @@ -2199,7 +3031,8 @@ static int cxd2841er_set_frontend_tc(struct dvb_frontend *fe) struct cxd2841er_priv *priv = fe->demodulator_priv; struct dtv_frontend_properties *p = &fe->dtv_property_cache; - dev_dbg(&priv->i2c->dev, "%s()\n", __func__); + dev_dbg(&priv->i2c->dev, "%s() delivery_system=%d bandwidth_hz=%d\n", + __func__, p->delivery_system, p->bandwidth_hz); if (p->delivery_system == SYS_DVBT) { priv->system = SYS_DVBT; switch (priv->state) { @@ -2233,9 +3066,33 @@ static int cxd2841er_set_frontend_tc(struct dvb_frontend *fe) __func__, priv->state); ret = -EINVAL; } + } else if (p->delivery_system == SYS_ISDBT) { + priv->system = SYS_ISDBT; + switch (priv->state) { + case STATE_SLEEP_TC: + ret = cxd2841er_sleep_tc_to_active_i( + priv, p->bandwidth_hz); + break; + case STATE_ACTIVE_TC: + ret = cxd2841er_retune_active(priv, p); + break; + default: + dev_dbg(&priv->i2c->dev, "%s(): invalid state %d\n", + __func__, priv->state); + ret = -EINVAL; + } } else if (p->delivery_system == SYS_DVBC_ANNEX_A || p->delivery_system == SYS_DVBC_ANNEX_C) { priv->system = SYS_DVBC_ANNEX_A; + /* correct bandwidth */ + if (p->bandwidth_hz != 6000000 && + p->bandwidth_hz != 7000000 && + p->bandwidth_hz != 8000000) { + p->bandwidth_hz = 8000000; + dev_dbg(&priv->i2c->dev, "%s(): forcing bandwidth to %d\n", + __func__, p->bandwidth_hz); + } + switch (priv->state) { case STATE_SLEEP_TC: ret = cxd2841er_sleep_tc_to_active_c( @@ -2321,7 +3178,8 @@ static int cxd2841er_tune_tc(struct dvb_frontend *fe, struct cxd2841er_priv *priv = fe->demodulator_priv; struct dtv_frontend_properties *p = &fe->dtv_property_cache; - dev_dbg(&priv->i2c->dev, "%s(): re_tune %d\n", __func__, re_tune); + dev_dbg(&priv->i2c->dev, "%s(): re_tune %d bandwidth=%d\n", __func__, + re_tune, p->bandwidth_hz); if (re_tune) { ret = cxd2841er_set_frontend_tc(fe); if (ret) @@ -2329,7 +3187,16 @@ static int cxd2841er_tune_tc(struct dvb_frontend *fe, cxd2841er_read_status_tc(fe, status); if (*status & FE_HAS_LOCK) { switch (priv->system) { + case SYS_ISDBT: + ret = cxd2841er_get_carrier_offset_i( + priv, p->bandwidth_hz, + &carrier_offset); + break; case SYS_DVBT: + ret = cxd2841er_get_carrier_offset_t( + priv, p->bandwidth_hz, + &carrier_offset); + break; case SYS_DVBT2: ret = cxd2841er_get_carrier_offset_t2( priv, p->bandwidth_hz, @@ -2382,6 +3249,9 @@ static int cxd2841er_sleep_tc(struct dvb_frontend *fe) case SYS_DVBT2: cxd2841er_active_t2_to_sleep_tc(priv); break; + case SYS_ISDBT: + cxd2841er_active_i_to_sleep_tc(priv); + break; case SYS_DVBC_ANNEX_A: cxd2841er_active_c_to_sleep_tc(priv); break; @@ -2516,6 +3386,18 @@ static int cxd2841er_init_s(struct dvb_frontend *fe) { struct cxd2841er_priv *priv = fe->demodulator_priv; + /* sanity. force demod to SHUTDOWN state */ + if (priv->state == STATE_SLEEP_S) { + dev_dbg(&priv->i2c->dev, "%s() forcing sleep->shutdown\n", + __func__); + cxd2841er_sleep_s_to_shutdown(priv); + } else if (priv->state == STATE_ACTIVE_S) { + dev_dbg(&priv->i2c->dev, "%s() forcing active->sleep->shutdown\n", + __func__); + cxd2841er_active_s_to_sleep_s(priv); + cxd2841er_sleep_s_to_shutdown(priv); + } + dev_dbg(&priv->i2c->dev, "%s()\n", __func__); cxd2841er_shutdown_to_sleep_s(priv); /* SONY_DEMOD_CONFIG_SAT_IFAGCNEG set to 1 */ @@ -2527,8 +3409,10 @@ static int cxd2841er_init_s(struct dvb_frontend *fe) static int cxd2841er_init_tc(struct dvb_frontend *fe) { struct cxd2841er_priv *priv = fe->demodulator_priv; + struct dtv_frontend_properties *p = &fe->dtv_property_cache; - dev_dbg(&priv->i2c->dev, "%s()\n", __func__); + dev_dbg(&priv->i2c->dev, "%s() bandwidth_hz=%d\n", + __func__, p->bandwidth_hz); cxd2841er_shutdown_to_sleep_tc(priv); /* SONY_DEMOD_CONFIG_IFAGCNEG = 1 */ cxd2841er_write_reg(priv, I2C_SLVT, 0x00, 0x10); @@ -2542,8 +3426,7 @@ static int cxd2841er_init_tc(struct dvb_frontend *fe) } static struct dvb_frontend_ops cxd2841er_dvbs_s2_ops; -static struct dvb_frontend_ops cxd2841er_dvbt_t2_ops; -static struct dvb_frontend_ops cxd2841er_dvbc_ops; +static struct dvb_frontend_ops cxd2841er_t_c_ops; static struct dvb_frontend *cxd2841er_attach(struct cxd2841er_config *cfg, struct i2c_adapter *i2c, @@ -2551,6 +3434,7 @@ static struct dvb_frontend *cxd2841er_attach(struct cxd2841er_config *cfg, { u8 chip_id = 0; const char *type; + const char *name; struct cxd2841er_priv *priv = NULL; /* allocate memory for the internal state */ @@ -2561,46 +3445,49 @@ static struct dvb_frontend *cxd2841er_attach(struct cxd2841er_config *cfg, priv->config = cfg; priv->i2c_addr_slvx = (cfg->i2c_addr + 4) >> 1; priv->i2c_addr_slvt = (cfg->i2c_addr) >> 1; - /* create dvb_frontend */ - switch (system) { - case SYS_DVBS: - memcpy(&priv->frontend.ops, - &cxd2841er_dvbs_s2_ops, - sizeof(struct dvb_frontend_ops)); - type = "S/S2"; - break; - case SYS_DVBT: - memcpy(&priv->frontend.ops, - &cxd2841er_dvbt_t2_ops, - sizeof(struct dvb_frontend_ops)); - type = "T/T2"; - break; - case SYS_DVBC_ANNEX_A: - memcpy(&priv->frontend.ops, - &cxd2841er_dvbc_ops, - sizeof(struct dvb_frontend_ops)); - type = "C/C2"; - break; - default: - kfree(priv); - return NULL; - } + priv->xtal = cfg->xtal; priv->frontend.demodulator_priv = priv; dev_info(&priv->i2c->dev, - "%s(): attaching CXD2841ER DVB-%s frontend\n", - __func__, type); - dev_info(&priv->i2c->dev, "%s(): I2C adapter %p SLVX addr %x SLVT addr %x\n", __func__, priv->i2c, priv->i2c_addr_slvx, priv->i2c_addr_slvt); chip_id = cxd2841er_chip_id(priv); - if (chip_id != CXD2841ER_CHIP_ID) { + switch (chip_id) { + case CXD2841ER_CHIP_ID: + snprintf(cxd2841er_t_c_ops.info.name, 128, + "Sony CXD2841ER DVB-T/T2/C demodulator"); + name = "CXD2841ER"; + break; + case CXD2854ER_CHIP_ID: + snprintf(cxd2841er_t_c_ops.info.name, 128, + "Sony CXD2854ER DVB-T/T2/C and ISDB-T demodulator"); + cxd2841er_t_c_ops.delsys[3] = SYS_ISDBT; + name = "CXD2854ER"; + break; + default: dev_err(&priv->i2c->dev, "%s(): invalid chip ID 0x%02x\n", - __func__, chip_id); + __func__, chip_id); priv->frontend.demodulator_priv = NULL; kfree(priv); return NULL; } + + /* create dvb_frontend */ + if (system == SYS_DVBS) { + memcpy(&priv->frontend.ops, + &cxd2841er_dvbs_s2_ops, + sizeof(struct dvb_frontend_ops)); + type = "S/S2"; + } else { + memcpy(&priv->frontend.ops, + &cxd2841er_t_c_ops, + sizeof(struct dvb_frontend_ops)); + type = "T/T2/C/ISDB-T"; + } + + dev_info(&priv->i2c->dev, + "%s(): attaching %s DVB-%s frontend\n", + __func__, name, type); dev_info(&priv->i2c->dev, "%s(): chip ID 0x%02x OK.\n", __func__, chip_id); return &priv->frontend; @@ -2613,19 +3500,12 @@ struct dvb_frontend *cxd2841er_attach_s(struct cxd2841er_config *cfg, } EXPORT_SYMBOL(cxd2841er_attach_s); -struct dvb_frontend *cxd2841er_attach_t(struct cxd2841er_config *cfg, +struct dvb_frontend *cxd2841er_attach_t_c(struct cxd2841er_config *cfg, struct i2c_adapter *i2c) { - return cxd2841er_attach(cfg, i2c, SYS_DVBT); + return cxd2841er_attach(cfg, i2c, 0); } -EXPORT_SYMBOL(cxd2841er_attach_t); - -struct dvb_frontend *cxd2841er_attach_c(struct cxd2841er_config *cfg, - struct i2c_adapter *i2c) -{ - return cxd2841er_attach(cfg, i2c, SYS_DVBC_ANNEX_A); -} -EXPORT_SYMBOL(cxd2841er_attach_c); +EXPORT_SYMBOL(cxd2841er_attach_t_c); static struct dvb_frontend_ops cxd2841er_dvbs_s2_ops = { .delsys = { SYS_DVBS, SYS_DVBS2 }, @@ -2655,10 +3535,10 @@ static struct dvb_frontend_ops cxd2841er_dvbs_s2_ops = { .tune = cxd2841er_tune_s }; -static struct dvb_frontend_ops cxd2841er_dvbt_t2_ops = { - .delsys = { SYS_DVBT, SYS_DVBT2 }, +static struct dvb_frontend_ops cxd2841er_t_c_ops = { + .delsys = { SYS_DVBT, SYS_DVBT2, SYS_DVBC_ANNEX_A }, .info = { - .name = "Sony CXD2841ER DVB-T/T2 demodulator", + .name = "", /* will set in attach function */ .caps = FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 | @@ -2691,37 +3571,6 @@ static struct dvb_frontend_ops cxd2841er_dvbt_t2_ops = { .get_frontend_algo = cxd2841er_get_algo }; -static struct dvb_frontend_ops cxd2841er_dvbc_ops = { - .delsys = { SYS_DVBC_ANNEX_A }, - .info = { - .name = "Sony CXD2841ER DVB-C demodulator", - .caps = FE_CAN_FEC_1_2 | - FE_CAN_FEC_2_3 | - FE_CAN_FEC_3_4 | - FE_CAN_FEC_5_6 | - FE_CAN_FEC_7_8 | - FE_CAN_FEC_AUTO | - FE_CAN_QAM_16 | - FE_CAN_QAM_32 | - FE_CAN_QAM_64 | - FE_CAN_QAM_128 | - FE_CAN_QAM_256 | - FE_CAN_QAM_AUTO | - FE_CAN_INVERSION_AUTO, - .frequency_min = 42000000, - .frequency_max = 1002000000 - }, - .init = cxd2841er_init_tc, - .sleep = cxd2841er_sleep_tc, - .release = cxd2841er_release, - .set_frontend = cxd2841er_set_frontend_tc, - .get_frontend = cxd2841er_get_frontend, - .read_status = cxd2841er_read_status_tc, - .tune = cxd2841er_tune_tc, - .i2c_gate_ctrl = cxd2841er_i2c_gate_ctrl, - .get_frontend_algo = cxd2841er_get_algo, -}; - -MODULE_DESCRIPTION("Sony CXD2841ER DVB-C/C2/T/T2/S/S2 demodulator driver"); -MODULE_AUTHOR("Sergey Kozlov <serjk@netup.ru>"); +MODULE_DESCRIPTION("Sony CXD2841ER/CXD2854ER DVB-C/C2/T/T2/S/S2 demodulator driver"); +MODULE_AUTHOR("Sergey Kozlov <serjk@netup.ru>, Abylay Ospan <aospan@netup.ru>"); MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb-frontends/cxd2841er.h b/drivers/media/dvb-frontends/cxd2841er.h index 3472bdd58949..62ad5f07390b 100644 --- a/drivers/media/dvb-frontends/cxd2841er.h +++ b/drivers/media/dvb-frontends/cxd2841er.h @@ -25,41 +25,39 @@ #include <linux/kconfig.h> #include <linux/dvb/frontend.h> +enum cxd2841er_xtal { + SONY_XTAL_20500, /* 20.5 MHz */ + SONY_XTAL_24000, /* 24 MHz */ + SONY_XTAL_41000 /* 41 MHz */ +}; + struct cxd2841er_config { u8 i2c_addr; + enum cxd2841er_xtal xtal; }; #if IS_REACHABLE(CONFIG_DVB_CXD2841ER) extern struct dvb_frontend *cxd2841er_attach_s(struct cxd2841er_config *cfg, struct i2c_adapter *i2c); -extern struct dvb_frontend *cxd2841er_attach_t(struct cxd2841er_config *cfg, - struct i2c_adapter *i2c); - -extern struct dvb_frontend *cxd2841er_attach_c(struct cxd2841er_config *cfg, +extern struct dvb_frontend *cxd2841er_attach_t_c(struct cxd2841er_config *cfg, struct i2c_adapter *i2c); #else static inline struct dvb_frontend *cxd2841er_attach_s( struct cxd2841er_config *cfg, struct i2c_adapter *i2c) { - printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + pr_warn("%s: driver disabled by Kconfig\n", __func__); return NULL; } -static inline struct dvb_frontend *cxd2841er_attach_t( +static inline struct dvb_frontend *cxd2841er_attach_t_c( struct cxd2841er_config *cfg, struct i2c_adapter *i2c) { - printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + pr_warn("%s: driver disabled by Kconfig\n", __func__); return NULL; } -static inline struct dvb_frontend *cxd2841er_attach_c( - struct cxd2841er_config *cfg, struct i2c_adapter *i2c) -{ - printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); - return NULL; -} #endif #endif diff --git a/drivers/media/dvb-frontends/cxd2841er_priv.h b/drivers/media/dvb-frontends/cxd2841er_priv.h index 33e2f495277b..0bbce451149f 100644 --- a/drivers/media/dvb-frontends/cxd2841er_priv.h +++ b/drivers/media/dvb-frontends/cxd2841er_priv.h @@ -26,6 +26,7 @@ #define I2C_SLVT 1 #define CXD2841ER_CHIP_ID 0xa7 +#define CXD2854ER_CHIP_ID 0xc1 #define CXD2841ER_DVBS_POLLING_INVL 10 diff --git a/drivers/media/dvb-frontends/dib0090.c b/drivers/media/dvb-frontends/dib0090.c index d879dc0607f4..14c403254fe0 100644 --- a/drivers/media/dvb-frontends/dib0090.c +++ b/drivers/media/dvb-frontends/dib0090.c @@ -797,6 +797,8 @@ static const u16 bb_ramp_pwm_normal[] = { (0 << 9) | 400, /* BB_RAMP6 */ }; +#if 0 +/* Currently unused */ static const u16 bb_ramp_pwm_boost[] = { 550, /* max BB gain in 10th of dB */ 8, /* ramp_slope = 1dB of gain -> clock_ticks_per_db = clk_khz / ramp_slope -> BB_RAMP2 */ @@ -806,6 +808,7 @@ static const u16 bb_ramp_pwm_boost[] = { (2 << 9) | 208, /* BB_RAMP5 = 29dB */ (0 << 9) | 440, /* BB_RAMP6 */ }; +#endif static const u16 rf_ramp_pwm_cband[] = { 314, /* max RF gain in 10th of dB */ @@ -849,6 +852,8 @@ static const u16 rf_ramp_pwm_uhf[] = { (0 << 10) | 580, /* GAIN_4_2, LNA 4 */ }; +#if 0 +/* Currently unused */ static const u16 rf_ramp_pwm_sband[] = { 253, /* max RF gain in 10th of dB */ 38, /* ramp_slope = 1dB of gain -> clock_ticks_per_db = clk_khz / ramp_slope -> RF_RAMP2 */ @@ -862,6 +867,7 @@ static const u16 rf_ramp_pwm_sband[] = { (0 << 10) | 0, /* GAIN_4_1, LNA 4 = 0dB */ (0 << 10) | 0, /* GAIN_4_2, LNA 4 */ }; +#endif struct slope { s16 range; diff --git a/drivers/media/dvb-frontends/drx39xyj/drxj.c b/drivers/media/dvb-frontends/drx39xyj/drxj.c index e48b741d439e..bd6d2ee0f7c9 100644 --- a/drivers/media/dvb-frontends/drx39xyj/drxj.c +++ b/drivers/media/dvb-frontends/drx39xyj/drxj.c @@ -1240,12 +1240,15 @@ static u32 frac_times1e6(u32 N, u32 D) * and rounded. For calc used formula: 16*10^(prescaleGain[dB]/20). * */ +#if 0 +/* Currently, unused as we lack support for analog TV */ static const u16 nicam_presc_table_val[43] = { 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 4, 4, 5, 5, 6, 6, 7, 8, 9, 10, 11, 13, 14, 16, 18, 20, 23, 25, 28, 32, 36, 40, 45, 51, 57, 64, 71, 80, 90, 101, 113, 127 }; +#endif /*============================================================================*/ /*== END HELPER FUNCTIONS ==*/ diff --git a/drivers/media/dvb-frontends/ds3000.c b/drivers/media/dvb-frontends/ds3000.c index addffc33993a..447b518e287a 100644 --- a/drivers/media/dvb-frontends/ds3000.c +++ b/drivers/media/dvb-frontends/ds3000.c @@ -959,6 +959,15 @@ static int ds3000_set_frontend(struct dvb_frontend *fe) /* enable ac coupling */ ds3000_writereg(state, 0x25, 0x8a); + if ((c->symbol_rate < ds3000_ops.info.symbol_rate_min) || + (c->symbol_rate > ds3000_ops.info.symbol_rate_max)) { + dprintk("%s() symbol_rate %u out of range (%u ... %u)\n", + __func__, c->symbol_rate, + ds3000_ops.info.symbol_rate_min, + ds3000_ops.info.symbol_rate_max); + return -EINVAL; + } + /* enhance symbol rate performance */ if ((c->symbol_rate / 1000) <= 5000) { value = 29777 / (c->symbol_rate / 1000) + 1; diff --git a/drivers/media/dvb-frontends/helene.c b/drivers/media/dvb-frontends/helene.c new file mode 100644 index 000000000000..97a8982740a6 --- /dev/null +++ b/drivers/media/dvb-frontends/helene.c @@ -0,0 +1,1042 @@ +/* + * helene.c + * + * Sony HELENE DVB-S/S2 DVB-T/T2 DVB-C/C2 ISDB-T/S tuner driver (CXD2858ER) + * + * Copyright 2012 Sony Corporation + * Copyright (C) 2014 NetUP Inc. + * Copyright (C) 2014 Abylay Ospan <aospan@netup.ru> + * + * 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/slab.h> +#include <linux/module.h> +#include <linux/dvb/frontend.h> +#include <linux/types.h> +#include "helene.h" +#include "dvb_frontend.h" + +#define MAX_WRITE_REGSIZE 20 + +enum helene_state { + STATE_UNKNOWN, + STATE_SLEEP, + STATE_ACTIVE +}; + +struct helene_priv { + u32 frequency; + u8 i2c_address; + struct i2c_adapter *i2c; + enum helene_state state; + void *set_tuner_data; + int (*set_tuner)(void *, int); + enum helene_xtal xtal; +}; + +#define TERR_INTERNAL_LOOPFILTER_AVAILABLE(tv_system) \ + (((tv_system) != SONY_HELENE_DTV_DVBC_6) && \ + ((tv_system) != SONY_HELENE_DTV_DVBC_8)\ + && ((tv_system) != SONY_HELENE_DTV_DVBC2_6) && \ + ((tv_system) != SONY_HELENE_DTV_DVBC2_8)) + +#define HELENE_AUTO 0xff +#define HELENE_OFFSET(ofs) ((u8)(ofs) & 0x1F) +#define HELENE_BW_6 0x00 +#define HELENE_BW_7 0x01 +#define HELENE_BW_8 0x02 +#define HELENE_BW_1_7 0x03 + +enum helene_tv_system_t { + SONY_HELENE_TV_SYSTEM_UNKNOWN, + /* Terrestrial Analog */ + SONY_HELENE_ATV_MN_EIAJ, + /**< System-M (Japan) (IF: Fp=5.75MHz in default) */ + SONY_HELENE_ATV_MN_SAP, + /**< System-M (US) (IF: Fp=5.75MHz in default) */ + SONY_HELENE_ATV_MN_A2, + /**< System-M (Korea) (IF: Fp=5.9MHz in default) */ + SONY_HELENE_ATV_BG, + /**< System-B/G (IF: Fp=7.3MHz in default) */ + SONY_HELENE_ATV_I, + /**< System-I (IF: Fp=7.85MHz in default) */ + SONY_HELENE_ATV_DK, + /**< System-D/K (IF: Fp=7.85MHz in default) */ + SONY_HELENE_ATV_L, + /**< System-L (IF: Fp=7.85MHz in default) */ + SONY_HELENE_ATV_L_DASH, + /**< System-L DASH (IF: Fp=2.2MHz in default) */ + /* Terrestrial/Cable Digital */ + SONY_HELENE_DTV_8VSB, + /**< ATSC 8VSB (IF: Fc=3.7MHz in default) */ + SONY_HELENE_DTV_QAM, + /**< US QAM (IF: Fc=3.7MHz in default) */ + SONY_HELENE_DTV_ISDBT_6, + /**< ISDB-T 6MHzBW (IF: Fc=3.55MHz in default) */ + SONY_HELENE_DTV_ISDBT_7, + /**< ISDB-T 7MHzBW (IF: Fc=4.15MHz in default) */ + SONY_HELENE_DTV_ISDBT_8, + /**< ISDB-T 8MHzBW (IF: Fc=4.75MHz in default) */ + SONY_HELENE_DTV_DVBT_5, + /**< DVB-T 5MHzBW (IF: Fc=3.6MHz in default) */ + SONY_HELENE_DTV_DVBT_6, + /**< DVB-T 6MHzBW (IF: Fc=3.6MHz in default) */ + SONY_HELENE_DTV_DVBT_7, + /**< DVB-T 7MHzBW (IF: Fc=4.2MHz in default) */ + SONY_HELENE_DTV_DVBT_8, + /**< DVB-T 8MHzBW (IF: Fc=4.8MHz in default) */ + SONY_HELENE_DTV_DVBT2_1_7, + /**< DVB-T2 1.7MHzBW (IF: Fc=3.5MHz in default) */ + SONY_HELENE_DTV_DVBT2_5, + /**< DVB-T2 5MHzBW (IF: Fc=3.6MHz in default) */ + SONY_HELENE_DTV_DVBT2_6, + /**< DVB-T2 6MHzBW (IF: Fc=3.6MHz in default) */ + SONY_HELENE_DTV_DVBT2_7, + /**< DVB-T2 7MHzBW (IF: Fc=4.2MHz in default) */ + SONY_HELENE_DTV_DVBT2_8, + /**< DVB-T2 8MHzBW (IF: Fc=4.8MHz in default) */ + SONY_HELENE_DTV_DVBC_6, + /**< DVB-C 6MHzBW (IF: Fc=3.7MHz in default) */ + SONY_HELENE_DTV_DVBC_8, + /**< DVB-C 8MHzBW (IF: Fc=4.9MHz in default) */ + SONY_HELENE_DTV_DVBC2_6, + /**< DVB-C2 6MHzBW (IF: Fc=3.7MHz in default) */ + SONY_HELENE_DTV_DVBC2_8, + /**< DVB-C2 8MHzBW (IF: Fc=4.9MHz in default) */ + SONY_HELENE_DTV_DTMB, + /**< DTMB (IF: Fc=5.1MHz in default) */ + /* Satellite */ + SONY_HELENE_STV_ISDBS, + /**< ISDB-S */ + SONY_HELENE_STV_DVBS, + /**< DVB-S */ + SONY_HELENE_STV_DVBS2, + /**< DVB-S2 */ + + SONY_HELENE_ATV_MIN = SONY_HELENE_ATV_MN_EIAJ, + /**< Minimum analog terrestrial system */ + SONY_HELENE_ATV_MAX = SONY_HELENE_ATV_L_DASH, + /**< Maximum analog terrestrial system */ + SONY_HELENE_DTV_MIN = SONY_HELENE_DTV_8VSB, + /**< Minimum digital terrestrial system */ + SONY_HELENE_DTV_MAX = SONY_HELENE_DTV_DTMB, + /**< Maximum digital terrestrial system */ + SONY_HELENE_TERR_TV_SYSTEM_NUM, + /**< Number of supported terrestrial broadcasting system */ + SONY_HELENE_STV_MIN = SONY_HELENE_STV_ISDBS, + /**< Minimum satellite system */ + SONY_HELENE_STV_MAX = SONY_HELENE_STV_DVBS2 + /**< Maximum satellite system */ +}; + +struct helene_terr_adjust_param_t { + /* < Addr:0x69 Bit[6:4] : RFVGA gain. + * 0xFF means Auto. (RF_GAIN_SEL = 1) + */ + uint8_t RF_GAIN; + /* < Addr:0x69 Bit[3:0] : IF_BPF gain. + */ + uint8_t IF_BPF_GC; + /* < Addr:0x6B Bit[3:0] : RF overload + * RF input detect level. (FRF <= 172MHz) + */ + uint8_t RFOVLD_DET_LV1_VL; + /* < Addr:0x6B Bit[3:0] : RF overload + * RF input detect level. (172MHz < FRF <= 464MHz) + */ + uint8_t RFOVLD_DET_LV1_VH; + /* < Addr:0x6B Bit[3:0] : RF overload + * RF input detect level. (FRF > 464MHz) + */ + uint8_t RFOVLD_DET_LV1_U; + /* < Addr:0x6C Bit[2:0] : + * Internal RFAGC detect level. (FRF <= 172MHz) + */ + uint8_t IFOVLD_DET_LV_VL; + /* < Addr:0x6C Bit[2:0] : + * Internal RFAGC detect level. (172MHz < FRF <= 464MHz) + */ + uint8_t IFOVLD_DET_LV_VH; + /* < Addr:0x6C Bit[2:0] : + * Internal RFAGC detect level. (FRF > 464MHz) + */ + uint8_t IFOVLD_DET_LV_U; + /* < Addr:0x6D Bit[5:4] : + * IF filter center offset. + */ + uint8_t IF_BPF_F0; + /* < Addr:0x6D Bit[1:0] : + * 6MHzBW(0x00) or 7MHzBW(0x01) + * or 8MHzBW(0x02) or 1.7MHzBW(0x03) + */ + uint8_t BW; + /* < Addr:0x6E Bit[4:0] : + * 5bit signed. IF offset (kHz) = FIF_OFFSET x 50 + */ + uint8_t FIF_OFFSET; + /* < Addr:0x6F Bit[4:0] : + * 5bit signed. BW offset (kHz) = + * BW_OFFSET x 50 (BW_OFFSET x 10 in 1.7MHzBW) + */ + uint8_t BW_OFFSET; + /* < Addr:0x9C Bit[0] : + * Local polarity. (0: Upper Local, 1: Lower Local) + */ + uint8_t IS_LOWERLOCAL; +}; + +static const struct helene_terr_adjust_param_t +terr_params[SONY_HELENE_TERR_TV_SYSTEM_NUM] = { + /*< SONY_HELENE_TV_SYSTEM_UNKNOWN */ + {HELENE_AUTO, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + HELENE_BW_6, HELENE_OFFSET(0), HELENE_OFFSET(0), 0x00}, + /* Analog */ + /**< SONY_HELENE_ATV_MN_EIAJ (System-M (Japan)) */ + {HELENE_AUTO, 0x05, 0x03, 0x06, 0x03, 0x01, 0x01, 0x01, 0x00, + HELENE_BW_6, HELENE_OFFSET(0), HELENE_OFFSET(1), 0x00}, + /**< SONY_HELENE_ATV_MN_SAP (System-M (US)) */ + {HELENE_AUTO, 0x05, 0x03, 0x06, 0x03, 0x01, 0x01, 0x01, 0x00, + HELENE_BW_6, HELENE_OFFSET(0), HELENE_OFFSET(1), 0x00}, + {HELENE_AUTO, 0x05, 0x03, 0x06, 0x03, 0x01, 0x01, 0x01, 0x00, + HELENE_BW_6, HELENE_OFFSET(3), HELENE_OFFSET(1), 0x00}, + /**< SONY_HELENE_ATV_MN_A2 (System-M (Korea)) */ + {HELENE_AUTO, 0x05, 0x03, 0x06, 0x03, 0x01, 0x01, 0x01, 0x00, + HELENE_BW_7, HELENE_OFFSET(11), HELENE_OFFSET(5), 0x00}, + /**< SONY_HELENE_ATV_BG (System-B/G) */ + {HELENE_AUTO, 0x05, 0x03, 0x06, 0x03, 0x01, 0x01, 0x01, 0x00, + HELENE_BW_8, HELENE_OFFSET(2), HELENE_OFFSET(-3), 0x00}, + /**< SONY_HELENE_ATV_I (System-I) */ + {HELENE_AUTO, 0x05, 0x03, 0x06, 0x03, 0x01, 0x01, 0x01, 0x00, + HELENE_BW_8, HELENE_OFFSET(2), HELENE_OFFSET(-3), 0x00}, + /**< SONY_HELENE_ATV_DK (System-D/K) */ + {HELENE_AUTO, 0x03, 0x04, 0x0A, 0x04, 0x04, 0x04, 0x04, 0x00, + HELENE_BW_8, HELENE_OFFSET(2), HELENE_OFFSET(-3), 0x00}, + /**< SONY_HELENE_ATV_L (System-L) */ + {HELENE_AUTO, 0x03, 0x04, 0x0A, 0x04, 0x04, 0x04, 0x04, 0x00, + HELENE_BW_8, HELENE_OFFSET(-1), HELENE_OFFSET(4), 0x00}, + /**< SONY_HELENE_ATV_L_DASH (System-L DASH) */ + /* Digital */ + {HELENE_AUTO, 0x09, 0x0B, 0x0B, 0x0B, 0x03, 0x03, 0x03, 0x00, + HELENE_BW_6, HELENE_OFFSET(-6), HELENE_OFFSET(-3), 0x00}, + /**< SONY_HELENE_DTV_8VSB (ATSC 8VSB) */ + {HELENE_AUTO, 0x09, 0x0B, 0x0B, 0x0B, 0x02, 0x02, 0x02, 0x00, + HELENE_BW_6, HELENE_OFFSET(-6), HELENE_OFFSET(-3), 0x00}, + /**< SONY_HELENE_DTV_QAM (US QAM) */ + {HELENE_AUTO, 0x09, 0x0B, 0x0B, 0x0B, 0x02, 0x02, 0x02, 0x00, + HELENE_BW_6, HELENE_OFFSET(-9), HELENE_OFFSET(-5), 0x00}, + /**< SONY_HELENE_DTV_ISDBT_6 (ISDB-T 6MHzBW) */ + {HELENE_AUTO, 0x09, 0x0B, 0x0B, 0x0B, 0x02, 0x02, 0x02, 0x00, + HELENE_BW_7, HELENE_OFFSET(-7), HELENE_OFFSET(-6), 0x00}, + /**< SONY_HELENE_DTV_ISDBT_7 (ISDB-T 7MHzBW) */ + {HELENE_AUTO, 0x09, 0x0B, 0x0B, 0x0B, 0x02, 0x02, 0x02, 0x00, + HELENE_BW_8, HELENE_OFFSET(-5), HELENE_OFFSET(-7), 0x00}, + /**< SONY_HELENE_DTV_ISDBT_8 (ISDB-T 8MHzBW) */ + {HELENE_AUTO, 0x09, 0x0B, 0x0B, 0x0B, 0x02, 0x02, 0x02, 0x00, + HELENE_BW_6, HELENE_OFFSET(-8), HELENE_OFFSET(-3), 0x00}, + /**< SONY_HELENE_DTV_DVBT_5 (DVB-T 5MHzBW) */ + {HELENE_AUTO, 0x09, 0x0B, 0x0B, 0x0B, 0x02, 0x02, 0x02, 0x00, + HELENE_BW_6, HELENE_OFFSET(-8), HELENE_OFFSET(-3), 0x00}, + /**< SONY_HELENE_DTV_DVBT_6 (DVB-T 6MHzBW) */ + {HELENE_AUTO, 0x09, 0x0B, 0x0B, 0x0B, 0x02, 0x02, 0x02, 0x00, + HELENE_BW_7, HELENE_OFFSET(-6), HELENE_OFFSET(-5), 0x00}, + /**< SONY_HELENE_DTV_DVBT_7 (DVB-T 7MHzBW) */ + {HELENE_AUTO, 0x09, 0x0B, 0x0B, 0x0B, 0x02, 0x02, 0x02, 0x00, + HELENE_BW_8, HELENE_OFFSET(-4), HELENE_OFFSET(-6), 0x00}, + /**< SONY_HELENE_DTV_DVBT_8 (DVB-T 8MHzBW) */ + {HELENE_AUTO, 0x09, 0x0B, 0x0B, 0x0B, 0x02, 0x02, 0x02, 0x00, + HELENE_BW_1_7, HELENE_OFFSET(-10), HELENE_OFFSET(-10), 0x00}, + /**< SONY_HELENE_DTV_DVBT2_1_7 (DVB-T2 1.7MHzBW) */ + {HELENE_AUTO, 0x09, 0x0B, 0x0B, 0x0B, 0x02, 0x02, 0x02, 0x00, + HELENE_BW_6, HELENE_OFFSET(-8), HELENE_OFFSET(-3), 0x00}, + /**< SONY_HELENE_DTV_DVBT2_5 (DVB-T2 5MHzBW) */ + {HELENE_AUTO, 0x09, 0x0B, 0x0B, 0x0B, 0x02, 0x02, 0x02, 0x00, + HELENE_BW_6, HELENE_OFFSET(-8), HELENE_OFFSET(-3), 0x00}, + /**< SONY_HELENE_DTV_DVBT2_6 (DVB-T2 6MHzBW) */ + {HELENE_AUTO, 0x09, 0x0B, 0x0B, 0x0B, 0x02, 0x02, 0x02, 0x00, + HELENE_BW_7, HELENE_OFFSET(-6), HELENE_OFFSET(-5), 0x00}, + /**< SONY_HELENE_DTV_DVBT2_7 (DVB-T2 7MHzBW) */ + {HELENE_AUTO, 0x09, 0x0B, 0x0B, 0x0B, 0x02, 0x02, 0x02, 0x00, + HELENE_BW_8, HELENE_OFFSET(-4), HELENE_OFFSET(-6), 0x00}, + /**< SONY_HELENE_DTV_DVBT2_8 (DVB-T2 8MHzBW) */ + {HELENE_AUTO, 0x05, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01, 0x00, + HELENE_BW_6, HELENE_OFFSET(-6), HELENE_OFFSET(-4), 0x00}, + /**< SONY_HELENE_DTV_DVBC_6 (DVB-C 6MHzBW) */ + {HELENE_AUTO, 0x05, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01, 0x00, + HELENE_BW_8, HELENE_OFFSET(-2), HELENE_OFFSET(-3), 0x00}, + /**< SONY_HELENE_DTV_DVBC_8 (DVB-C 8MHzBW) */ + {HELENE_AUTO, 0x03, 0x09, 0x09, 0x09, 0x02, 0x02, 0x02, 0x00, + HELENE_BW_6, HELENE_OFFSET(-6), HELENE_OFFSET(-2), 0x00}, + /**< SONY_HELENE_DTV_DVBC2_6 (DVB-C2 6MHzBW) */ + {HELENE_AUTO, 0x03, 0x09, 0x09, 0x09, 0x02, 0x02, 0x02, 0x00, + HELENE_BW_8, HELENE_OFFSET(-2), HELENE_OFFSET(0), 0x00}, + /**< SONY_HELENE_DTV_DVBC2_8 (DVB-C2 8MHzBW) */ + {HELENE_AUTO, 0x04, 0x0B, 0x0B, 0x0B, 0x02, 0x02, 0x02, 0x00, + HELENE_BW_8, HELENE_OFFSET(2), HELENE_OFFSET(1), 0x00} + /**< SONY_HELENE_DTV_DTMB (DTMB) */ +}; + +static void helene_i2c_debug(struct helene_priv *priv, + u8 reg, u8 write, const u8 *data, u32 len) +{ + dev_dbg(&priv->i2c->dev, "helene: I2C %s reg 0x%02x size %d\n", + (write == 0 ? "read" : "write"), reg, len); + print_hex_dump_bytes("helene: I2C data: ", + DUMP_PREFIX_OFFSET, data, len); +} + +static int helene_write_regs(struct helene_priv *priv, + u8 reg, const u8 *data, u32 len) +{ + int ret; + u8 buf[MAX_WRITE_REGSIZE + 1]; + struct i2c_msg msg[1] = { + { + .addr = priv->i2c_address, + .flags = 0, + .len = len + 1, + .buf = buf, + } + }; + + if (len + 1 > sizeof(buf)) { + dev_warn(&priv->i2c->dev, + "wr reg=%04x: len=%d vs %Zu is too big!\n", + reg, len + 1, sizeof(buf)); + return -E2BIG; + } + + helene_i2c_debug(priv, reg, 1, data, len); + buf[0] = reg; + memcpy(&buf[1], data, len); + ret = i2c_transfer(priv->i2c, msg, 1); + if (ret >= 0 && ret != 1) + ret = -EREMOTEIO; + if (ret < 0) { + dev_warn(&priv->i2c->dev, + "%s: i2c wr failed=%d reg=%02x len=%d\n", + KBUILD_MODNAME, ret, reg, len); + return ret; + } + return 0; +} + +static int helene_write_reg(struct helene_priv *priv, u8 reg, u8 val) +{ + return helene_write_regs(priv, reg, &val, 1); +} + +static int helene_read_regs(struct helene_priv *priv, + u8 reg, u8 *val, u32 len) +{ + int ret; + struct i2c_msg msg[2] = { + { + .addr = priv->i2c_address, + .flags = 0, + .len = 1, + .buf = ®, + }, { + .addr = priv->i2c_address, + .flags = I2C_M_RD, + .len = len, + .buf = val, + } + }; + + ret = i2c_transfer(priv->i2c, &msg[0], 1); + if (ret >= 0 && ret != 1) + ret = -EREMOTEIO; + if (ret < 0) { + dev_warn(&priv->i2c->dev, + "%s: I2C rw failed=%d addr=%02x reg=%02x\n", + KBUILD_MODNAME, ret, priv->i2c_address, reg); + return ret; + } + ret = i2c_transfer(priv->i2c, &msg[1], 1); + if (ret >= 0 && ret != 1) + ret = -EREMOTEIO; + if (ret < 0) { + dev_warn(&priv->i2c->dev, + "%s: i2c rd failed=%d addr=%02x reg=%02x\n", + KBUILD_MODNAME, ret, priv->i2c_address, reg); + return ret; + } + helene_i2c_debug(priv, reg, 0, val, len); + return 0; +} + +static int helene_read_reg(struct helene_priv *priv, u8 reg, u8 *val) +{ + return helene_read_regs(priv, reg, val, 1); +} + +static int helene_set_reg_bits(struct helene_priv *priv, + u8 reg, u8 data, u8 mask) +{ + int res; + u8 rdata; + + if (mask != 0xff) { + res = helene_read_reg(priv, reg, &rdata); + if (res != 0) + return res; + data = ((data & mask) | (rdata & (mask ^ 0xFF))); + } + return helene_write_reg(priv, reg, data); +} + +static int helene_enter_power_save(struct helene_priv *priv) +{ + dev_dbg(&priv->i2c->dev, "%s()\n", __func__); + if (priv->state == STATE_SLEEP) + return 0; + + /* Standby setting for CPU */ + helene_write_reg(priv, 0x88, 0x0); + + /* Standby setting for internal logic block */ + helene_write_reg(priv, 0x87, 0xC0); + + priv->state = STATE_SLEEP; + return 0; +} + +static int helene_leave_power_save(struct helene_priv *priv) +{ + dev_dbg(&priv->i2c->dev, "%s()\n", __func__); + if (priv->state == STATE_ACTIVE) + return 0; + + /* Standby setting for internal logic block */ + helene_write_reg(priv, 0x87, 0xC4); + + /* Standby setting for CPU */ + helene_write_reg(priv, 0x88, 0x40); + + priv->state = STATE_ACTIVE; + return 0; +} + +static int helene_init(struct dvb_frontend *fe) +{ + struct helene_priv *priv = fe->tuner_priv; + + dev_dbg(&priv->i2c->dev, "%s()\n", __func__); + return helene_leave_power_save(priv); +} + +static int helene_release(struct dvb_frontend *fe) +{ + struct helene_priv *priv = fe->tuner_priv; + + dev_dbg(&priv->i2c->dev, "%s()\n", __func__); + kfree(fe->tuner_priv); + fe->tuner_priv = NULL; + return 0; +} + +static int helene_sleep(struct dvb_frontend *fe) +{ + struct helene_priv *priv = fe->tuner_priv; + + dev_dbg(&priv->i2c->dev, "%s()\n", __func__); + helene_enter_power_save(priv); + return 0; +} + +static enum helene_tv_system_t helene_get_tv_system(struct dvb_frontend *fe) +{ + enum helene_tv_system_t system = SONY_HELENE_TV_SYSTEM_UNKNOWN; + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + struct helene_priv *priv = fe->tuner_priv; + + if (p->delivery_system == SYS_DVBT) { + if (p->bandwidth_hz <= 5000000) + system = SONY_HELENE_DTV_DVBT_5; + else if (p->bandwidth_hz <= 6000000) + system = SONY_HELENE_DTV_DVBT_6; + else if (p->bandwidth_hz <= 7000000) + system = SONY_HELENE_DTV_DVBT_7; + else if (p->bandwidth_hz <= 8000000) + system = SONY_HELENE_DTV_DVBT_8; + else { + system = SONY_HELENE_DTV_DVBT_8; + p->bandwidth_hz = 8000000; + } + } else if (p->delivery_system == SYS_DVBT2) { + if (p->bandwidth_hz <= 5000000) + system = SONY_HELENE_DTV_DVBT2_5; + else if (p->bandwidth_hz <= 6000000) + system = SONY_HELENE_DTV_DVBT2_6; + else if (p->bandwidth_hz <= 7000000) + system = SONY_HELENE_DTV_DVBT2_7; + else if (p->bandwidth_hz <= 8000000) + system = SONY_HELENE_DTV_DVBT2_8; + else { + system = SONY_HELENE_DTV_DVBT2_8; + p->bandwidth_hz = 8000000; + } + } else if (p->delivery_system == SYS_DVBS) { + system = SONY_HELENE_STV_DVBS; + } else if (p->delivery_system == SYS_DVBS2) { + system = SONY_HELENE_STV_DVBS2; + } else if (p->delivery_system == SYS_ISDBS) { + system = SONY_HELENE_STV_ISDBS; + } else if (p->delivery_system == SYS_ISDBT) { + if (p->bandwidth_hz <= 6000000) + system = SONY_HELENE_DTV_ISDBT_6; + else if (p->bandwidth_hz <= 7000000) + system = SONY_HELENE_DTV_ISDBT_7; + else if (p->bandwidth_hz <= 8000000) + system = SONY_HELENE_DTV_ISDBT_8; + else { + system = SONY_HELENE_DTV_ISDBT_8; + p->bandwidth_hz = 8000000; + } + } else if (p->delivery_system == SYS_DVBC_ANNEX_A) { + if (p->bandwidth_hz <= 6000000) + system = SONY_HELENE_DTV_DVBC_6; + else if (p->bandwidth_hz <= 8000000) + system = SONY_HELENE_DTV_DVBC_8; + } + dev_dbg(&priv->i2c->dev, + "%s(): HELENE DTV system %d (delsys %d, bandwidth %d)\n", + __func__, (int)system, p->delivery_system, + p->bandwidth_hz); + return system; +} + +static int helene_set_params_s(struct dvb_frontend *fe) +{ + u8 data[MAX_WRITE_REGSIZE]; + u32 frequency; + enum helene_tv_system_t tv_system; + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + struct helene_priv *priv = fe->tuner_priv; + int frequencykHz = p->frequency; + uint32_t frequency4kHz = 0; + u32 symbol_rate = p->symbol_rate/1000; + + dev_dbg(&priv->i2c->dev, "%s(): tune frequency %dkHz sr=%uKsps\n", + __func__, frequencykHz, symbol_rate); + tv_system = helene_get_tv_system(fe); + + if (tv_system == SONY_HELENE_TV_SYSTEM_UNKNOWN) { + dev_err(&priv->i2c->dev, "%s(): unknown DTV system\n", + __func__); + return -EINVAL; + } + /* RF switch turn to satellite */ + if (priv->set_tuner) + priv->set_tuner(priv->set_tuner_data, 0); + frequency = roundup(p->frequency / 1000, 1); + + /* Disable IF signal output */ + helene_write_reg(priv, 0x15, 0x02); + + /* RFIN matching in power save (Sat) reset */ + helene_write_reg(priv, 0x43, 0x06); + + /* Analog block setting (0x6A, 0x6B) */ + data[0] = 0x00; + data[1] = 0x00; + helene_write_regs(priv, 0x6A, data, 2); + helene_write_reg(priv, 0x75, 0x99); + helene_write_reg(priv, 0x9D, 0x00); + + /* Tuning setting for CPU (0x61) */ + helene_write_reg(priv, 0x61, 0x07); + + /* Satellite mode select (0x01) */ + helene_write_reg(priv, 0x01, 0x01); + + /* Clock enable for internal logic block, CPU wake-up (0x04, 0x05) */ + data[0] = 0xC4; + data[1] = 0x40; + + switch (priv->xtal) { + case SONY_HELENE_XTAL_16000: + data[2] = 0x02; + break; + case SONY_HELENE_XTAL_20500: + data[2] = 0x02; + break; + case SONY_HELENE_XTAL_24000: + data[2] = 0x03; + break; + case SONY_HELENE_XTAL_41000: + data[2] = 0x05; + break; + default: + dev_err(&priv->i2c->dev, "%s(): unknown xtal %d\n", + __func__, priv->xtal); + return -EINVAL; + } + + /* Setting for analog block (0x07). LOOPFILTER INTERNAL */ + data[3] = 0x80; + + /* Tuning setting for analog block + * (0x08, 0x09, 0x0A, 0x0B). LOOPFILTER INTERNAL + */ + if (priv->xtal == SONY_HELENE_XTAL_20500) + data[4] = 0x58; + else + data[4] = 0x70; + + data[5] = 0x1E; + data[6] = 0x02; + data[7] = 0x24; + + /* Enable for analog block (0x0C, 0x0D, 0x0E). SAT LNA ON */ + data[8] = 0x0F; + data[8] |= 0xE0; /* POWERSAVE_TERR_RF_ACTIVE */ + data[9] = 0x02; + data[10] = 0x1E; + + /* Setting for LPF cutoff frequency (0x0F) */ + switch (tv_system) { + case SONY_HELENE_STV_ISDBS: + data[11] = 0x22; /* 22MHz */ + break; + case SONY_HELENE_STV_DVBS: + if (symbol_rate <= 4000) + data[11] = 0x05; + else if (symbol_rate <= 10000) + data[11] = (uint8_t)((symbol_rate * 47 + + (40000-1)) / 40000); + else + data[11] = (uint8_t)((symbol_rate * 27 + + (40000-1)) / 40000 + 5); + + if (data[11] > 36) + data[11] = 36; /* 5 <= lpf_cutoff <= 36 is valid */ + break; + case SONY_HELENE_STV_DVBS2: + if (symbol_rate <= 4000) + data[11] = 0x05; + else if (symbol_rate <= 10000) + data[11] = (uint8_t)((symbol_rate * 11 + + (10000-1)) / 10000); + else + data[11] = (uint8_t)((symbol_rate * 3 + + (5000-1)) / 5000 + 5); + + if (data[11] > 36) + data[11] = 36; /* 5 <= lpf_cutoff <= 36 is valid */ + break; + default: + dev_err(&priv->i2c->dev, "%s(): unknown standard %d\n", + __func__, tv_system); + return -EINVAL; + } + + /* RF tuning frequency setting (0x10, 0x11, 0x12) */ + frequency4kHz = (frequencykHz + 2) / 4; + data[12] = (uint8_t)(frequency4kHz & 0xFF); /* FRF_L */ + data[13] = (uint8_t)((frequency4kHz >> 8) & 0xFF); /* FRF_M */ + /* FRF_H (bit[3:0]) */ + data[14] = (uint8_t)((frequency4kHz >> 16) & 0x0F); + + /* Tuning command (0x13) */ + data[15] = 0xFF; + + /* Setting for IQOUT_LIMIT (0x14) 0.75Vpp */ + data[16] = 0x00; + + /* Enable IQ output (0x15) */ + data[17] = 0x01; + + helene_write_regs(priv, 0x04, data, 18); + + dev_dbg(&priv->i2c->dev, "%s(): tune done\n", + __func__); + + priv->frequency = frequency; + return 0; +} + +static int helene_set_params(struct dvb_frontend *fe) +{ + u8 data[MAX_WRITE_REGSIZE]; + u32 frequency; + enum helene_tv_system_t tv_system; + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + struct helene_priv *priv = fe->tuner_priv; + int frequencykHz = p->frequency / 1000; + + dev_dbg(&priv->i2c->dev, "%s(): tune frequency %dkHz\n", + __func__, frequencykHz); + tv_system = helene_get_tv_system(fe); + + if (tv_system == SONY_HELENE_TV_SYSTEM_UNKNOWN) { + dev_dbg(&priv->i2c->dev, "%s(): unknown DTV system\n", + __func__); + return -EINVAL; + } + if (priv->set_tuner) + priv->set_tuner(priv->set_tuner_data, 1); + frequency = roundup(p->frequency / 1000, 25); + + /* mode select */ + helene_write_reg(priv, 0x01, 0x00); + + /* Disable IF signal output */ + helene_write_reg(priv, 0x74, 0x02); + + if (priv->state == STATE_SLEEP) + helene_leave_power_save(priv); + + /* Initial setting for internal analog block (0x91, 0x92) */ + if ((tv_system == SONY_HELENE_DTV_DVBC_6) || + (tv_system == SONY_HELENE_DTV_DVBC_8)) { + data[0] = 0x16; + data[1] = 0x26; + } else { + data[0] = 0x10; + data[1] = 0x20; + } + helene_write_regs(priv, 0x91, data, 2); + + /* Setting for analog block */ + if (TERR_INTERNAL_LOOPFILTER_AVAILABLE(tv_system)) + data[0] = 0x90; + else + data[0] = 0x00; + + /* Setting for local polarity (0x9D) */ + data[1] = (uint8_t)(terr_params[tv_system].IS_LOWERLOCAL & 0x01); + helene_write_regs(priv, 0x9C, data, 2); + + /* Enable for analog block */ + data[0] = 0xEE; + data[1] = 0x02; + data[2] = 0x1E; + data[3] = 0x67; /* Tuning setting for CPU */ + + /* Setting for PLL reference divider for xtal=24MHz */ + if ((tv_system == SONY_HELENE_DTV_DVBC_6) || + (tv_system == SONY_HELENE_DTV_DVBC_8)) + data[4] = 0x18; + else + data[4] = 0x03; + + /* Tuning setting for analog block */ + if (TERR_INTERNAL_LOOPFILTER_AVAILABLE(tv_system)) { + data[5] = 0x38; + data[6] = 0x1E; + data[7] = 0x02; + data[8] = 0x24; + } else if ((tv_system == SONY_HELENE_DTV_DVBC_6) || + (tv_system == SONY_HELENE_DTV_DVBC_8)) { + data[5] = 0x1C; + data[6] = 0x78; + data[7] = 0x08; + data[8] = 0x1C; + } else { + data[5] = 0xB4; + data[6] = 0x78; + data[7] = 0x08; + data[8] = 0x30; + } + helene_write_regs(priv, 0x5E, data, 9); + + /* LT_AMP_EN should be 0 */ + helene_set_reg_bits(priv, 0x67, 0x0, 0x02); + + /* Setting for IFOUT_LIMIT */ + data[0] = 0x00; /* 1.5Vpp */ + + /* RF_GAIN setting */ + if (terr_params[tv_system].RF_GAIN == HELENE_AUTO) + data[1] = 0x80; /* RF_GAIN_SEL = 1 */ + else + data[1] = (uint8_t)((terr_params[tv_system].RF_GAIN + << 4) & 0x70); + + /* IF_BPF_GC setting */ + data[1] |= (uint8_t)(terr_params[tv_system].IF_BPF_GC & 0x0F); + + /* Setting for internal RFAGC (0x6A, 0x6B, 0x6C) */ + data[2] = 0x00; + if (frequencykHz <= 172000) { + data[3] = (uint8_t)(terr_params[tv_system].RFOVLD_DET_LV1_VL + & 0x0F); + data[4] = (uint8_t)(terr_params[tv_system].IFOVLD_DET_LV_VL + & 0x07); + } else if (frequencykHz <= 464000) { + data[3] = (uint8_t)(terr_params[tv_system].RFOVLD_DET_LV1_VH + & 0x0F); + data[4] = (uint8_t)(terr_params[tv_system].IFOVLD_DET_LV_VH + & 0x07); + } else { + data[3] = (uint8_t)(terr_params[tv_system].RFOVLD_DET_LV1_U + & 0x0F); + data[4] = (uint8_t)(terr_params[tv_system].IFOVLD_DET_LV_U + & 0x07); + } + data[4] |= 0x20; + + /* Setting for IF frequency and bandwidth */ + + /* IF filter center frequency offset (IF_BPF_F0) (0x6D) */ + data[5] = (uint8_t)((terr_params[tv_system].IF_BPF_F0 << 4) & 0x30); + + /* IF filter band width (BW) (0x6D) */ + data[5] |= (uint8_t)(terr_params[tv_system].BW & 0x03); + + /* IF frequency offset value (FIF_OFFSET) (0x6E) */ + data[6] = (uint8_t)(terr_params[tv_system].FIF_OFFSET & 0x1F); + + /* IF band width offset value (BW_OFFSET) (0x6F) */ + data[7] = (uint8_t)(terr_params[tv_system].BW_OFFSET & 0x1F); + + /* RF tuning frequency setting (0x70, 0x71, 0x72) */ + data[8] = (uint8_t)(frequencykHz & 0xFF); /* FRF_L */ + data[9] = (uint8_t)((frequencykHz >> 8) & 0xFF); /* FRF_M */ + data[10] = (uint8_t)((frequencykHz >> 16) + & 0x0F); /* FRF_H (bit[3:0]) */ + + /* Tuning command */ + data[11] = 0xFF; + + /* Enable IF output, AGC and IFOUT pin selection (0x74) */ + data[12] = 0x01; + + if ((tv_system == SONY_HELENE_DTV_DVBC_6) || + (tv_system == SONY_HELENE_DTV_DVBC_8)) { + data[13] = 0xD9; + data[14] = 0x0F; + data[15] = 0x24; + data[16] = 0x87; + } else { + data[13] = 0x99; + data[14] = 0x00; + data[15] = 0x24; + data[16] = 0x87; + } + + helene_write_regs(priv, 0x68, data, 17); + + dev_dbg(&priv->i2c->dev, "%s(): tune done\n", + __func__); + + priv->frequency = frequency; + return 0; +} + +static int helene_get_frequency(struct dvb_frontend *fe, u32 *frequency) +{ + struct helene_priv *priv = fe->tuner_priv; + + *frequency = priv->frequency * 1000; + return 0; +} + +static struct dvb_tuner_ops helene_tuner_ops = { + .info = { + .name = "Sony HELENE Ter tuner", + .frequency_min = 1000000, + .frequency_max = 1200000000, + .frequency_step = 25000, + }, + .init = helene_init, + .release = helene_release, + .sleep = helene_sleep, + .set_params = helene_set_params, + .get_frequency = helene_get_frequency, +}; + +static struct dvb_tuner_ops helene_tuner_ops_s = { + .info = { + .name = "Sony HELENE Sat tuner", + .frequency_min = 500000, + .frequency_max = 2500000, + .frequency_step = 1000, + }, + .init = helene_init, + .release = helene_release, + .sleep = helene_sleep, + .set_params = helene_set_params_s, + .get_frequency = helene_get_frequency, +}; + +/* power-on tuner + * call once after reset + */ +static int helene_x_pon(struct helene_priv *priv) +{ + /* RFIN matching in power save (terrestrial) = ACTIVE */ + /* RFIN matching in power save (satellite) = ACTIVE */ + u8 dataT[] = { 0x06, 0x00, 0x02, 0x00 }; + /* SAT_RF_ACTIVE = true, lnaOff = false, terrRfActive = true */ + u8 dataS[] = { 0x05, 0x06 }; + u8 cdata[] = {0x7A, 0x01}; + u8 data[20]; + u8 rdata[2]; + + /* mode select */ + helene_write_reg(priv, 0x01, 0x00); + + helene_write_reg(priv, 0x67, dataT[3]); + helene_write_reg(priv, 0x43, dataS[1]); + helene_write_regs(priv, 0x5E, dataT, 3); + helene_write_reg(priv, 0x0C, dataS[0]); + + /* Initial setting for internal logic block */ + helene_write_regs(priv, 0x99, cdata, sizeof(cdata)); + + /* 0x81 - 0x94 */ + data[0] = 0x18; /* xtal 24 MHz */ + data[1] = (uint8_t)(0x80 | (0x04 & 0x1F)); /* 4 x 25 = 100uA */ + data[2] = (uint8_t)(0x80 | (0x26 & 0x7F)); /* 38 x 0.25 = 9.5pF */ + data[3] = 0x80; /* REFOUT signal output 500mVpp */ + data[4] = 0x00; /* GPIO settings */ + data[5] = 0x00; /* GPIO settings */ + data[6] = 0xC4; /* Clock enable for internal logic block */ + data[7] = 0x40; /* Start CPU boot-up */ + data[8] = 0x10; /* For burst-write */ + + /* Setting for internal RFAGC */ + data[9] = 0x00; + data[10] = 0x45; + data[11] = 0x75; + + data[12] = 0x07; /* Setting for analog block */ + + /* Initial setting for internal analog block */ + data[13] = 0x1C; + data[14] = 0x3F; + data[15] = 0x02; + data[16] = 0x10; + data[17] = 0x20; + data[18] = 0x0A; + data[19] = 0x00; + + helene_write_regs(priv, 0x81, data, sizeof(data)); + + /* Setting for internal RFAGC */ + helene_write_reg(priv, 0x9B, 0x00); + + msleep(20); + + /* Check CPU_STT/CPU_ERR */ + helene_read_regs(priv, 0x1A, rdata, sizeof(rdata)); + + if (rdata[0] != 0x00) { + dev_err(&priv->i2c->dev, + "HELENE tuner CPU error 0x%x\n", rdata[0]); + return -EIO; + } + + /* VCO current setting */ + cdata[0] = 0x90; + cdata[1] = 0x06; + helene_write_regs(priv, 0x17, cdata, sizeof(cdata)); + msleep(20); + helene_read_reg(priv, 0x19, data); + helene_write_reg(priv, 0x95, (uint8_t)((data[0] >> 4) & 0x0F)); + + /* Disable IF signal output */ + helene_write_reg(priv, 0x74, 0x02); + + /* Standby setting for CPU */ + helene_write_reg(priv, 0x88, 0x00); + + /* Standby setting for internal logic block */ + helene_write_reg(priv, 0x87, 0xC0); + + /* Load capacitance control setting for crystal oscillator */ + helene_write_reg(priv, 0x80, 0x01); + + /* Satellite initial setting */ + cdata[0] = 0x07; + cdata[1] = 0x00; + helene_write_regs(priv, 0x41, cdata, sizeof(cdata)); + + dev_info(&priv->i2c->dev, + "HELENE tuner x_pon done\n"); + + return 0; +} + +struct dvb_frontend *helene_attach_s(struct dvb_frontend *fe, + const struct helene_config *config, + struct i2c_adapter *i2c) +{ + struct helene_priv *priv = NULL; + + priv = kzalloc(sizeof(struct helene_priv), GFP_KERNEL); + if (priv == NULL) + return NULL; + priv->i2c_address = (config->i2c_address >> 1); + priv->i2c = i2c; + priv->set_tuner_data = config->set_tuner_priv; + priv->set_tuner = config->set_tuner_callback; + priv->xtal = config->xtal; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + + if (helene_x_pon(priv) != 0) + return NULL; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 0); + + memcpy(&fe->ops.tuner_ops, &helene_tuner_ops_s, + sizeof(struct dvb_tuner_ops)); + fe->tuner_priv = priv; + dev_info(&priv->i2c->dev, + "Sony HELENE Sat attached on addr=%x at I2C adapter %p\n", + priv->i2c_address, priv->i2c); + return fe; +} +EXPORT_SYMBOL(helene_attach_s); + +struct dvb_frontend *helene_attach(struct dvb_frontend *fe, + const struct helene_config *config, + struct i2c_adapter *i2c) +{ + struct helene_priv *priv = NULL; + + priv = kzalloc(sizeof(struct helene_priv), GFP_KERNEL); + if (priv == NULL) + return NULL; + priv->i2c_address = (config->i2c_address >> 1); + priv->i2c = i2c; + priv->set_tuner_data = config->set_tuner_priv; + priv->set_tuner = config->set_tuner_callback; + priv->xtal = config->xtal; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + + if (helene_x_pon(priv) != 0) + return NULL; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 0); + + memcpy(&fe->ops.tuner_ops, &helene_tuner_ops, + sizeof(struct dvb_tuner_ops)); + fe->tuner_priv = priv; + dev_info(&priv->i2c->dev, + "Sony HELENE Ter attached on addr=%x at I2C adapter %p\n", + priv->i2c_address, priv->i2c); + return fe; +} +EXPORT_SYMBOL(helene_attach); + +MODULE_DESCRIPTION("Sony HELENE Sat/Ter tuner driver"); +MODULE_AUTHOR("Abylay Ospan <aospan@netup.ru>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb-frontends/helene.h b/drivers/media/dvb-frontends/helene.h new file mode 100644 index 000000000000..e1b9224cfc55 --- /dev/null +++ b/drivers/media/dvb-frontends/helene.h @@ -0,0 +1,79 @@ +/* + * helene.h + * + * Sony HELENE DVB-S/S2/T/T2/C/C2/ISDB-T/S tuner driver (CXD2858ER) + * + * Copyright 2012 Sony Corporation + * Copyright (C) 2014 NetUP Inc. + * Copyright (C) 2014 Abylay Ospan <aospan@netup.ru> + * + * 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. + */ + +#ifndef __DVB_HELENE_H__ +#define __DVB_HELENE_H__ + +#include <linux/kconfig.h> +#include <linux/dvb/frontend.h> +#include <linux/i2c.h> + +enum helene_xtal { + SONY_HELENE_XTAL_16000, /* 16 MHz */ + SONY_HELENE_XTAL_20500, /* 20.5 MHz */ + SONY_HELENE_XTAL_24000, /* 24 MHz */ + SONY_HELENE_XTAL_41000 /* 41 MHz */ +}; + +/** + * struct helene_config - the configuration of 'Helene' tuner driver + * @i2c_address: I2C address of the tuner + * @xtal_freq_mhz: Oscillator frequency, MHz + * @set_tuner_priv: Callback function private context + * @set_tuner_callback: Callback function that notifies the parent driver + * which tuner is active now + */ +struct helene_config { + u8 i2c_address; + u8 xtal_freq_mhz; + void *set_tuner_priv; + int (*set_tuner_callback)(void *, int); + enum helene_xtal xtal; +}; + +#if IS_REACHABLE(CONFIG_DVB_HELENE) +extern struct dvb_frontend *helene_attach(struct dvb_frontend *fe, + const struct helene_config *config, + struct i2c_adapter *i2c); +#else +static inline struct dvb_frontend *helene_attach(struct dvb_frontend *fe, + const struct helene_config *config, + struct i2c_adapter *i2c) +{ + pr_warn("%s: driver disabled by Kconfig\n", __func__); + return NULL; +} +#endif + +#if IS_REACHABLE(CONFIG_DVB_HELENE) +extern struct dvb_frontend *helene_attach_s(struct dvb_frontend *fe, + const struct helene_config *config, + struct i2c_adapter *i2c); +#else +static inline struct dvb_frontend *helene_attach_s(struct dvb_frontend *fe, + const struct helene_config *config, + struct i2c_adapter *i2c) +{ + pr_warn("%s: driver disabled by Kconfig\n", __func__); + return NULL; +} +#endif + +#endif diff --git a/drivers/media/dvb-frontends/horus3a.c b/drivers/media/dvb-frontends/horus3a.c index 000606af70f7..a98bca5270d9 100644 --- a/drivers/media/dvb-frontends/horus3a.c +++ b/drivers/media/dvb-frontends/horus3a.c @@ -66,7 +66,7 @@ static int horus3a_write_regs(struct horus3a_priv *priv, } }; - if (len + 1 >= sizeof(buf)) { + if (len + 1 > sizeof(buf)) { dev_warn(&priv->i2c->dev,"wr reg=%04x: len=%d is too big!\n", reg, len + 1); return -E2BIG; @@ -272,24 +272,6 @@ static int horus3a_set_params(struct dvb_frontend *fe) if (fc_lpf > 36) fc_lpf = 36; } else if (p->delivery_system == SYS_DVBS2) { - int rolloff; - - switch (p->rolloff) { - case ROLLOFF_35: - rolloff = 35; - break; - case ROLLOFF_25: - rolloff = 25; - break; - case ROLLOFF_20: - rolloff = 20; - break; - case ROLLOFF_AUTO: - default: - dev_err(&priv->i2c->dev, - "horus3a: auto roll-off is not supported\n"); - return -EINVAL; - } /* * SR <= 4.5: * fc_lpf = 5 @@ -302,11 +284,9 @@ static int horus3a_set_params(struct dvb_frontend *fe) if (symbol_rate <= 4500) fc_lpf = 5; else if (symbol_rate <= 10000) - fc_lpf = (u8)DIV_ROUND_UP( - symbol_rate * (200 + rolloff), 200000); + fc_lpf = (u8)((symbol_rate * 11 + (10000-1)) / 10000); else - fc_lpf = (u8)DIV_ROUND_UP( - symbol_rate * (100 + rolloff), 200000) + 5; + fc_lpf = (u8)((symbol_rate * 3 + (5000-1)) / 5000 + 5); /* 5 <= fc_lpf <= 36 is valid */ if (fc_lpf > 36) fc_lpf = 36; diff --git a/drivers/media/dvb-frontends/m88rs2000.c b/drivers/media/dvb-frontends/m88rs2000.c index a09b12313a73..ef79a4ec31e2 100644 --- a/drivers/media/dvb-frontends/m88rs2000.c +++ b/drivers/media/dvb-frontends/m88rs2000.c @@ -609,7 +609,7 @@ static int m88rs2000_set_frontend(struct dvb_frontend *fe) { struct m88rs2000_state *state = fe->demodulator_priv; struct dtv_frontend_properties *c = &fe->dtv_property_cache; - enum fe_status status; + enum fe_status status = 0; int i, ret = 0; u32 tuner_freq; s16 offset = 0; diff --git a/drivers/staging/media/mn88472/mn88472.c b/drivers/media/dvb-frontends/mn88472.c index 7ea749cf19f9..18fb2df1e2bd 100644 --- a/drivers/staging/media/mn88472/mn88472.c +++ b/drivers/media/dvb-frontends/mn88472.c @@ -17,28 +17,90 @@ #include "mn88472_priv.h" static int mn88472_get_tune_settings(struct dvb_frontend *fe, - struct dvb_frontend_tune_settings *s) + struct dvb_frontend_tune_settings *s) { - s->min_delay_ms = 800; + s->min_delay_ms = 1000; return 0; } +static int mn88472_read_status(struct dvb_frontend *fe, enum fe_status *status) +{ + struct i2c_client *client = fe->demodulator_priv; + struct mn88472_dev *dev = i2c_get_clientdata(client); + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret; + unsigned int utmp; + + if (!dev->active) { + ret = -EAGAIN; + goto err; + } + + switch (c->delivery_system) { + case SYS_DVBT: + ret = regmap_read(dev->regmap[0], 0x7f, &utmp); + if (ret) + goto err; + if ((utmp & 0x0f) >= 0x09) + *status = FE_HAS_SIGNAL | FE_HAS_CARRIER | + FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK; + else + *status = 0; + break; + case SYS_DVBT2: + ret = regmap_read(dev->regmap[2], 0x92, &utmp); + if (ret) + goto err; + if ((utmp & 0x0f) >= 0x0d) + *status = FE_HAS_SIGNAL | FE_HAS_CARRIER | + FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK; + else if ((utmp & 0x0f) >= 0x0a) + *status = FE_HAS_SIGNAL | FE_HAS_CARRIER | + FE_HAS_VITERBI; + else if ((utmp & 0x0f) >= 0x07) + *status = FE_HAS_SIGNAL | FE_HAS_CARRIER; + else + *status = 0; + break; + case SYS_DVBC_ANNEX_A: + ret = regmap_read(dev->regmap[1], 0x84, &utmp); + if (ret) + goto err; + if ((utmp & 0x0f) >= 0x08) + *status = FE_HAS_SIGNAL | FE_HAS_CARRIER | + FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK; + else + *status = 0; + break; + default: + ret = -EINVAL; + goto err; + } + + return 0; +err: + dev_dbg(&client->dev, "failed=%d\n", ret); + return ret; +} + static int mn88472_set_frontend(struct dvb_frontend *fe) { struct i2c_client *client = fe->demodulator_priv; struct mn88472_dev *dev = i2c_get_clientdata(client); struct dtv_frontend_properties *c = &fe->dtv_property_cache; int ret, i; - u32 if_frequency = 0; - u64 tmp; - u8 delivery_system_val, if_val[3], bw_val[7], bw_val2; + unsigned int utmp; + u32 if_frequency; + u8 buf[3], delivery_system_val, bandwidth_val, *bandwidth_vals_ptr; + u8 reg_bank0_b4_val, reg_bank0_cd_val, reg_bank0_d4_val; + u8 reg_bank0_d6_val; dev_dbg(&client->dev, - "delivery_system=%d modulation=%d frequency=%d symbol_rate=%d inversion=%d\n", - c->delivery_system, c->modulation, - c->frequency, c->symbol_rate, c->inversion); + "delivery_system=%u modulation=%u frequency=%u bandwidth_hz=%u symbol_rate=%u inversion=%d stream_id=%d\n", + c->delivery_system, c->modulation, c->frequency, + c->bandwidth_hz, c->symbol_rate, c->inversion, c->stream_id); - if (!dev->warm) { + if (!dev->active) { ret = -EAGAIN; goto err; } @@ -46,39 +108,64 @@ static int mn88472_set_frontend(struct dvb_frontend *fe) switch (c->delivery_system) { case SYS_DVBT: delivery_system_val = 0x02; + reg_bank0_b4_val = 0x00; + reg_bank0_cd_val = 0x1f; + reg_bank0_d4_val = 0x0a; + reg_bank0_d6_val = 0x48; break; case SYS_DVBT2: delivery_system_val = 0x03; + reg_bank0_b4_val = 0xf6; + reg_bank0_cd_val = 0x01; + reg_bank0_d4_val = 0x09; + reg_bank0_d6_val = 0x46; break; case SYS_DVBC_ANNEX_A: delivery_system_val = 0x04; + reg_bank0_b4_val = 0x00; + reg_bank0_cd_val = 0x17; + reg_bank0_d4_val = 0x09; + reg_bank0_d6_val = 0x48; break; default: ret = -EINVAL; goto err; } - if (c->bandwidth_hz <= 5000000) { - memcpy(bw_val, "\xe5\x99\x9a\x1b\xa9\x1b\xa9", 7); - bw_val2 = 0x03; - } else if (c->bandwidth_hz <= 6000000) { - /* IF 3570000 Hz, BW 6000000 Hz */ - memcpy(bw_val, "\xbf\x55\x55\x15\x6b\x15\x6b", 7); - bw_val2 = 0x02; - } else if (c->bandwidth_hz <= 7000000) { - /* IF 4570000 Hz, BW 7000000 Hz */ - memcpy(bw_val, "\xa4\x00\x00\x0f\x2c\x0f\x2c", 7); - bw_val2 = 0x01; - } else if (c->bandwidth_hz <= 8000000) { - /* IF 4570000 Hz, BW 8000000 Hz */ - memcpy(bw_val, "\x8f\x80\x00\x08\xee\x08\xee", 7); - bw_val2 = 0x00; - } else { - ret = -EINVAL; - goto err; + switch (c->delivery_system) { + case SYS_DVBT: + case SYS_DVBT2: + switch (c->bandwidth_hz) { + case 5000000: + bandwidth_vals_ptr = "\xe5\x99\x9a\x1b\xa9\x1b\xa9"; + bandwidth_val = 0x03; + break; + case 6000000: + bandwidth_vals_ptr = "\xbf\x55\x55\x15\x6b\x15\x6b"; + bandwidth_val = 0x02; + break; + case 7000000: + bandwidth_vals_ptr = "\xa4\x00\x00\x0f\x2c\x0f\x2c"; + bandwidth_val = 0x01; + break; + case 8000000: + bandwidth_vals_ptr = "\x8f\x80\x00\x08\xee\x08\xee"; + bandwidth_val = 0x00; + break; + default: + ret = -EINVAL; + goto err; + } + break; + case SYS_DVBC_ANNEX_A: + bandwidth_vals_ptr = NULL; + bandwidth_val = 0x00; + break; + default: + break; } - /* program tuner */ + /* Program tuner */ if (fe->ops.tuner_ops.set_params) { ret = fe->ops.tuner_ops.set_params(fe); if (ret) @@ -91,20 +178,10 @@ static int mn88472_set_frontend(struct dvb_frontend *fe) goto err; dev_dbg(&client->dev, "get_if_frequency=%d\n", if_frequency); - } - - /* Calculate IF registers ( (1<<24)*IF / Xtal ) */ - tmp = div_u64(if_frequency * (u64)(1<<24) + (dev->xtal / 2), - dev->xtal); - if_val[0] = (tmp >> 16) & 0xff; - if_val[1] = (tmp >> 8) & 0xff; - if_val[2] = (tmp >> 0) & 0xff; - - ret = regmap_write(dev->regmap[2], 0xfb, 0x13); - ret = regmap_write(dev->regmap[2], 0xef, 0x13); - ret = regmap_write(dev->regmap[2], 0xf9, 0x13); - if (ret) + } else { + ret = -EINVAL; goto err; + } ret = regmap_write(dev->regmap[2], 0x00, 0x66); if (ret) @@ -118,157 +195,81 @@ static int mn88472_set_frontend(struct dvb_frontend *fe) ret = regmap_write(dev->regmap[2], 0x03, delivery_system_val); if (ret) goto err; - ret = regmap_write(dev->regmap[2], 0x04, bw_val2); + ret = regmap_write(dev->regmap[2], 0x04, bandwidth_val); if (ret) goto err; - for (i = 0; i < sizeof(if_val); i++) { - ret = regmap_write(dev->regmap[2], 0x10 + i, if_val[i]); + /* IF */ + utmp = DIV_ROUND_CLOSEST_ULL((u64)if_frequency * 0x1000000, dev->clk); + buf[0] = (utmp >> 16) & 0xff; + buf[1] = (utmp >> 8) & 0xff; + buf[2] = (utmp >> 0) & 0xff; + for (i = 0; i < 3; i++) { + ret = regmap_write(dev->regmap[2], 0x10 + i, buf[i]); if (ret) goto err; } - for (i = 0; i < sizeof(bw_val); i++) { - ret = regmap_write(dev->regmap[2], 0x13 + i, bw_val[i]); - if (ret) - goto err; + /* Bandwidth */ + if (bandwidth_vals_ptr) { + for (i = 0; i < 7; i++) { + ret = regmap_write(dev->regmap[2], 0x13 + i, + bandwidth_vals_ptr[i]); + if (ret) + goto err; + } } + ret = regmap_write(dev->regmap[0], 0xb4, reg_bank0_b4_val); + if (ret) + goto err; + ret = regmap_write(dev->regmap[0], 0xcd, reg_bank0_cd_val); + if (ret) + goto err; + ret = regmap_write(dev->regmap[0], 0xd4, reg_bank0_d4_val); + if (ret) + goto err; + ret = regmap_write(dev->regmap[0], 0xd6, reg_bank0_d6_val); + if (ret) + goto err; + switch (c->delivery_system) { case SYS_DVBT: ret = regmap_write(dev->regmap[0], 0x07, 0x26); - ret = regmap_write(dev->regmap[0], 0xb0, 0x0a); - ret = regmap_write(dev->regmap[0], 0xb4, 0x00); - ret = regmap_write(dev->regmap[0], 0xcd, 0x1f); - ret = regmap_write(dev->regmap[0], 0xd4, 0x0a); - ret = regmap_write(dev->regmap[0], 0xd6, 0x48); + if (ret) + goto err; ret = regmap_write(dev->regmap[0], 0x00, 0xba); + if (ret) + goto err; ret = regmap_write(dev->regmap[0], 0x01, 0x13); if (ret) goto err; break; case SYS_DVBT2: ret = regmap_write(dev->regmap[2], 0x2b, 0x13); + if (ret) + goto err; ret = regmap_write(dev->regmap[2], 0x4f, 0x05); + if (ret) + goto err; ret = regmap_write(dev->regmap[1], 0xf6, 0x05); - ret = regmap_write(dev->regmap[0], 0xb0, 0x0a); - ret = regmap_write(dev->regmap[0], 0xb4, 0xf6); - ret = regmap_write(dev->regmap[0], 0xcd, 0x01); - ret = regmap_write(dev->regmap[0], 0xd4, 0x09); - ret = regmap_write(dev->regmap[0], 0xd6, 0x46); - ret = regmap_write(dev->regmap[2], 0x30, 0x80); - ret = regmap_write(dev->regmap[2], 0x32, 0x00); if (ret) goto err; - break; - case SYS_DVBC_ANNEX_A: - ret = regmap_write(dev->regmap[0], 0xb0, 0x0b); - ret = regmap_write(dev->regmap[0], 0xb4, 0x00); - ret = regmap_write(dev->regmap[0], 0xcd, 0x17); - ret = regmap_write(dev->regmap[0], 0xd4, 0x09); - ret = regmap_write(dev->regmap[0], 0xd6, 0x48); - ret = regmap_write(dev->regmap[1], 0x00, 0xb0); + ret = regmap_write(dev->regmap[2], 0x32, c->stream_id); if (ret) goto err; break; - default: - ret = -EINVAL; - goto err; - } - - ret = regmap_write(dev->regmap[0], 0x46, 0x00); - ret = regmap_write(dev->regmap[0], 0xae, 0x00); - - switch (dev->ts_mode) { - case SERIAL_TS_MODE: - ret = regmap_write(dev->regmap[2], 0x08, 0x1d); - break; - case PARALLEL_TS_MODE: - ret = regmap_write(dev->regmap[2], 0x08, 0x00); + case SYS_DVBC_ANNEX_A: break; default: - dev_dbg(&client->dev, "ts_mode error: %d\n", dev->ts_mode); - ret = -EINVAL; - goto err; - } - - switch (dev->ts_clock) { - case VARIABLE_TS_CLOCK: - ret = regmap_write(dev->regmap[0], 0xd9, 0xe3); break; - case FIXED_TS_CLOCK: - ret = regmap_write(dev->regmap[0], 0xd9, 0xe1); - break; - default: - dev_dbg(&client->dev, "ts_clock error: %d\n", dev->ts_clock); - ret = -EINVAL; - goto err; } - /* Reset demod */ + /* Reset FSM */ ret = regmap_write(dev->regmap[2], 0xf8, 0x9f); if (ret) goto err; - dev->delivery_system = c->delivery_system; - - return 0; -err: - dev_dbg(&client->dev, "failed=%d\n", ret); - return ret; -} - -static int mn88472_read_status(struct dvb_frontend *fe, enum fe_status *status) -{ - struct i2c_client *client = fe->demodulator_priv; - struct mn88472_dev *dev = i2c_get_clientdata(client); - struct dtv_frontend_properties *c = &fe->dtv_property_cache; - int ret; - unsigned int utmp; - int lock = 0; - - *status = 0; - - if (!dev->warm) { - ret = -EAGAIN; - goto err; - } - - switch (c->delivery_system) { - case SYS_DVBT: - ret = regmap_read(dev->regmap[0], 0x7F, &utmp); - if (ret) - goto err; - if ((utmp & 0xF) >= 0x09) - lock = 1; - break; - case SYS_DVBT2: - ret = regmap_read(dev->regmap[2], 0x92, &utmp); - if (ret) - goto err; - if ((utmp & 0xF) >= 0x07) - *status |= FE_HAS_SIGNAL; - if ((utmp & 0xF) >= 0x0a) - *status |= FE_HAS_CARRIER; - if ((utmp & 0xF) >= 0x0d) - *status |= FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK; - break; - case SYS_DVBC_ANNEX_A: - ret = regmap_read(dev->regmap[1], 0x84, &utmp); - if (ret) - goto err; - if ((utmp & 0xF) >= 0x08) - lock = 1; - break; - default: - ret = -EINVAL; - goto err; - } - - if (lock) - *status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_VITERBI | - FE_HAS_SYNC | FE_HAS_LOCK; - return 0; err: dev_dbg(&client->dev, "failed=%d\n", ret); @@ -279,93 +280,107 @@ static int mn88472_init(struct dvb_frontend *fe) { struct i2c_client *client = fe->demodulator_priv; struct mn88472_dev *dev = i2c_get_clientdata(client); - int ret, len, remaining; - const struct firmware *fw = NULL; - u8 *fw_file = MN88472_FIRMWARE; - unsigned int tmp; + int ret, len, rem; + unsigned int utmp; + const struct firmware *firmware; + const char *name = MN88472_FIRMWARE; dev_dbg(&client->dev, "\n"); - /* set cold state by default */ - dev->warm = false; - - /* power on */ + /* Power up */ ret = regmap_write(dev->regmap[2], 0x05, 0x00); if (ret) goto err; - - ret = regmap_bulk_write(dev->regmap[2], 0x0b, "\x00\x00", 2); + ret = regmap_write(dev->regmap[2], 0x0b, 0x00); if (ret) goto err; - - /* check if firmware is already running */ - ret = regmap_read(dev->regmap[0], 0xf5, &tmp); + ret = regmap_write(dev->regmap[2], 0x0c, 0x00); if (ret) goto err; - if (!(tmp & 0x1)) { - dev_info(&client->dev, "firmware already running\n"); - dev->warm = true; - return 0; - } + /* Check if firmware is already running */ + ret = regmap_read(dev->regmap[0], 0xf5, &utmp); + if (ret) + goto err; + if (!(utmp & 0x01)) + goto warm; - /* request the firmware, this will block and timeout */ - ret = request_firmware(&fw, fw_file, &client->dev); + ret = request_firmware(&firmware, name, &client->dev); if (ret) { - dev_err(&client->dev, "firmare file '%s' not found\n", - fw_file); + dev_err(&client->dev, "firmware file '%s' not found\n", name); goto err; } - dev_info(&client->dev, "downloading firmware from file '%s'\n", - fw_file); + dev_info(&client->dev, "downloading firmware from file '%s'\n", name); ret = regmap_write(dev->regmap[0], 0xf5, 0x03); if (ret) - goto firmware_release; - - for (remaining = fw->size; remaining > 0; - remaining -= (dev->i2c_wr_max - 1)) { - len = remaining; - if (len > (dev->i2c_wr_max - 1)) - len = dev->i2c_wr_max - 1; + goto err_release_firmware; + for (rem = firmware->size; rem > 0; rem -= (dev->i2c_write_max - 1)) { + len = min(dev->i2c_write_max - 1, rem); ret = regmap_bulk_write(dev->regmap[0], 0xf6, - &fw->data[fw->size - remaining], len); + &firmware->data[firmware->size - rem], + len); if (ret) { - dev_err(&client->dev, - "firmware download failed=%d\n", ret); - goto firmware_release; + dev_err(&client->dev, "firmware download failed %d\n", + ret); + goto err_release_firmware; } } - /* parity check of firmware */ - ret = regmap_read(dev->regmap[0], 0xf8, &tmp); - if (ret) { - dev_err(&client->dev, - "parity reg read failed=%d\n", ret); - goto firmware_release; - } - if (tmp & 0x10) { - dev_err(&client->dev, - "firmware parity check failed=0x%x\n", tmp); - goto firmware_release; + /* Parity check of firmware */ + ret = regmap_read(dev->regmap[0], 0xf8, &utmp); + if (ret) + goto err_release_firmware; + if (utmp & 0x10) { + ret = -EINVAL; + dev_err(&client->dev, "firmware did not run\n"); + goto err_release_firmware; } - dev_err(&client->dev, "firmware parity check succeeded=0x%x\n", tmp); ret = regmap_write(dev->regmap[0], 0xf5, 0x00); if (ret) - goto firmware_release; + goto err_release_firmware; + + release_firmware(firmware); +warm: + /* TS config */ + switch (dev->ts_mode) { + case SERIAL_TS_MODE: + utmp = 0x1d; + break; + case PARALLEL_TS_MODE: + utmp = 0x00; + break; + default: + ret = -EINVAL; + goto err; + } + ret = regmap_write(dev->regmap[2], 0x08, utmp); + if (ret) + goto err; - release_firmware(fw); - fw = NULL; + switch (dev->ts_clk) { + case VARIABLE_TS_CLOCK: + utmp = 0xe3; + break; + case FIXED_TS_CLOCK: + utmp = 0xe1; + break; + default: + ret = -EINVAL; + goto err; + } + ret = regmap_write(dev->regmap[0], 0xd9, utmp); + if (ret) + goto err; - /* warm state */ - dev->warm = true; + dev->active = true; return 0; -firmware_release: - release_firmware(fw); +err_release_firmware: + release_firmware(firmware); err: dev_dbg(&client->dev, "failed=%d\n", ret); return ret; @@ -379,18 +394,17 @@ static int mn88472_sleep(struct dvb_frontend *fe) dev_dbg(&client->dev, "\n"); - /* power off */ + /* Power down */ + ret = regmap_write(dev->regmap[2], 0x0c, 0x30); + if (ret) + goto err; ret = regmap_write(dev->regmap[2], 0x0b, 0x30); - if (ret) goto err; - ret = regmap_write(dev->regmap[2], 0x05, 0x3e); if (ret) goto err; - dev->delivery_system = SYS_UNDEFINED; - return 0; err: dev_dbg(&client->dev, "failed=%d\n", ret); @@ -434,10 +448,19 @@ static struct dvb_frontend_ops mn88472_ops = { .read_status = mn88472_read_status, }; +static struct dvb_frontend *mn88472_get_dvb_frontend(struct i2c_client *client) +{ + struct mn88472_dev *dev = i2c_get_clientdata(client); + + dev_dbg(&client->dev, "\n"); + + return &dev->fe; +} + static int mn88472_probe(struct i2c_client *client, - const struct i2c_device_id *id) + const struct i2c_device_id *id) { - struct mn88472_config *config = client->dev.platform_data; + struct mn88472_config *pdata = client->dev.platform_data; struct mn88472_dev *dev; int ret; unsigned int utmp; @@ -448,23 +471,16 @@ static int mn88472_probe(struct i2c_client *client, dev_dbg(&client->dev, "\n"); - /* Caller really need to provide pointer for frontend we create. */ - if (config->fe == NULL) { - dev_err(&client->dev, "frontend pointer not defined\n"); - ret = -EINVAL; - goto err; - } - dev = kzalloc(sizeof(*dev), GFP_KERNEL); if (!dev) { ret = -ENOMEM; goto err; } - dev->i2c_wr_max = config->i2c_wr_max; - dev->xtal = config->xtal; - dev->ts_mode = config->ts_mode; - dev->ts_clock = config->ts_clock; + dev->i2c_write_max = pdata->i2c_wr_max ? pdata->i2c_wr_max : ~0; + dev->clk = pdata->xtal; + dev->ts_mode = pdata->ts_mode; + dev->ts_clk = pdata->ts_clock; dev->client[0] = client; dev->regmap[0] = regmap_init_i2c(dev->client[0], ®map_config); if (IS_ERR(dev->regmap[0])) { @@ -472,15 +488,25 @@ static int mn88472_probe(struct i2c_client *client, goto err_kfree; } - /* check demod answers to I2C */ - ret = regmap_read(dev->regmap[0], 0x00, &utmp); + /* Check demod answers with correct chip id */ + ret = regmap_read(dev->regmap[0], 0xff, &utmp); if (ret) goto err_regmap_0_regmap_exit; + dev_dbg(&client->dev, "chip id=%02x\n", utmp); + + if (utmp != 0x02) { + ret = -ENODEV; + goto err_regmap_0_regmap_exit; + } + /* - * Chip has three I2C addresses for different register pages. Used + * Chip has three I2C addresses for different register banks. Used * addresses are 0x18, 0x1a and 0x1c. We register two dummy clients, - * 0x1a and 0x1c, in order to get own I2C client for each register page. + * 0x1a and 0x1c, in order to get own I2C client for each register bank. + * + * Also, register bank 2 do not support sequential I/O. Only single + * register write or read is allowed to that bank. */ dev->client[1] = i2c_new_dummy(client->adapter, 0x1a); if (!dev->client[1]) { @@ -510,15 +536,25 @@ static int mn88472_probe(struct i2c_client *client, } i2c_set_clientdata(dev->client[2], dev); - /* create dvb_frontend */ + /* Sleep because chip is active by default */ + ret = regmap_write(dev->regmap[2], 0x05, 0x3e); + if (ret) + goto err_regmap_2_regmap_exit; + + /* Create dvb frontend */ memcpy(&dev->fe.ops, &mn88472_ops, sizeof(struct dvb_frontend_ops)); dev->fe.demodulator_priv = client; - *config->fe = &dev->fe; + *pdata->fe = &dev->fe; i2c_set_clientdata(client, dev); - dev_info(&client->dev, "Panasonic MN88472 successfully attached\n"); - return 0; + /* Setup callbacks */ + pdata->get_dvb_frontend = mn88472_get_dvb_frontend; + dev_info(&client->dev, "Panasonic MN88472 successfully identified\n"); + + return 0; +err_regmap_2_regmap_exit: + regmap_exit(dev->regmap[2]); err_client_2_i2c_unregister_device: i2c_unregister_device(dev->client[2]); err_regmap_1_regmap_exit: @@ -561,11 +597,12 @@ MODULE_DEVICE_TABLE(i2c, mn88472_id_table); static struct i2c_driver mn88472_driver = { .driver = { - .name = "mn88472", + .name = "mn88472", + .suppress_bind_attrs = true, }, - .probe = mn88472_probe, - .remove = mn88472_remove, - .id_table = mn88472_id_table, + .probe = mn88472_probe, + .remove = mn88472_remove, + .id_table = mn88472_id_table, }; module_i2c_driver(mn88472_driver); diff --git a/drivers/media/dvb-frontends/mn88472.h b/drivers/media/dvb-frontends/mn88472.h index 095294d292f3..323632523876 100644 --- a/drivers/media/dvb-frontends/mn88472.h +++ b/drivers/media/dvb-frontends/mn88472.h @@ -19,23 +19,33 @@ #include <linux/dvb/frontend.h> -enum ts_clock { - VARIABLE_TS_CLOCK, - FIXED_TS_CLOCK, -}; +/** + * struct mn88472_config - Platform data for the mn88472 driver + * @xtal: Clock frequency. + * @ts_mode: TS mode. + * @ts_clock: TS clock config. + * @i2c_wr_max: Max number of bytes driver writes to I2C at once. + * @get_dvb_frontend: Get DVB frontend. + */ -enum ts_mode { - SERIAL_TS_MODE, - PARALLEL_TS_MODE, -}; +/* Define old names for backward compatibility */ +#define VARIABLE_TS_CLOCK MN88472_TS_CLK_VARIABLE +#define FIXED_TS_CLOCK MN88472_TS_CLK_FIXED +#define SERIAL_TS_MODE MN88472_TS_MODE_SERIAL +#define PARALLEL_TS_MODE MN88472_TS_MODE_PARALLEL struct mn88472_config { - /* - * Max num of bytes given I2C adapter could write at once. - * Default: none - */ - u16 i2c_wr_max; + unsigned int xtal; + +#define MN88472_TS_MODE_SERIAL 0 +#define MN88472_TS_MODE_PARALLEL 1 + int ts_mode; +#define MN88472_TS_CLK_FIXED 0 +#define MN88472_TS_CLK_VARIABLE 1 + int ts_clock; + + u16 i2c_wr_max; /* Everything after that is returned by the driver. */ @@ -43,14 +53,7 @@ struct mn88472_config { * DVB frontend. */ struct dvb_frontend **fe; - - /* - * Xtal frequency. - * Hz - */ - u32 xtal; - int ts_mode; - int ts_clock; + struct dvb_frontend* (*get_dvb_frontend)(struct i2c_client *); }; #endif diff --git a/drivers/staging/media/mn88472/mn88472_priv.h b/drivers/media/dvb-frontends/mn88472_priv.h index 1a0de9e46b66..cdf2597a25d1 100644 --- a/drivers/staging/media/mn88472/mn88472_priv.h +++ b/drivers/media/dvb-frontends/mn88472_priv.h @@ -28,12 +28,11 @@ struct mn88472_dev { struct i2c_client *client[3]; struct regmap *regmap[3]; struct dvb_frontend fe; - u16 i2c_wr_max; - enum fe_delivery_system delivery_system; - bool warm; /* FW running */ - u32 xtal; - int ts_mode; - int ts_clock; + u16 i2c_write_max; + unsigned int clk; + unsigned int active:1; + unsigned int ts_mode:1; + unsigned int ts_clk:1; }; #endif diff --git a/drivers/media/dvb-frontends/mn88473.c b/drivers/media/dvb-frontends/mn88473.c index 6c5d592161d4..451974a1d7ed 100644 --- a/drivers/media/dvb-frontends/mn88473.c +++ b/drivers/media/dvb-frontends/mn88473.c @@ -330,7 +330,7 @@ static int mn88473_init(struct dvb_frontend *fe) /* Request the firmware, this will block and timeout */ ret = request_firmware(&fw, name, &client->dev); if (ret) { - dev_err(&client->dev, "firmare file '%s' not found\n", name); + dev_err(&client->dev, "firmware file '%s' not found\n", name); goto err; } @@ -536,7 +536,7 @@ static int mn88473_probe(struct i2c_client *client, /* Sleep because chip is active by default */ ret = regmap_write(dev->regmap[2], 0x05, 0x3e); if (ret) - goto err_client_2_i2c_unregister_device; + goto err_regmap_2_regmap_exit; /* Create dvb frontend */ memcpy(&dev->frontend.ops, &mn88473_ops, sizeof(dev->frontend.ops)); @@ -547,7 +547,8 @@ static int mn88473_probe(struct i2c_client *client, dev_info(&client->dev, "Panasonic MN88473 successfully identified\n"); return 0; - +err_regmap_2_regmap_exit: + regmap_exit(dev->regmap[2]); err_client_2_i2c_unregister_device: i2c_unregister_device(dev->client[2]); err_regmap_1_regmap_exit: diff --git a/drivers/media/dvb-frontends/rtl2832.c b/drivers/media/dvb-frontends/rtl2832.c index bfb6beedd40b..c16c69e9d26c 100644 --- a/drivers/media/dvb-frontends/rtl2832.c +++ b/drivers/media/dvb-frontends/rtl2832.c @@ -947,6 +947,8 @@ static int rtl2832_slave_ts_ctrl(struct i2c_client *client, bool enable) goto err; } + dev->slave_ts = enable; + return 0; err: dev_dbg(&client->dev, "failed=%d\n", ret); @@ -960,7 +962,7 @@ static int rtl2832_pid_filter_ctrl(struct dvb_frontend *fe, int onoff) int ret; u8 u8tmp; - dev_dbg(&client->dev, "onoff=%d\n", onoff); + dev_dbg(&client->dev, "onoff=%d, slave_ts=%d\n", onoff, dev->slave_ts); /* enable / disable PID filter */ if (onoff) @@ -968,7 +970,10 @@ static int rtl2832_pid_filter_ctrl(struct dvb_frontend *fe, int onoff) else u8tmp = 0x00; - ret = regmap_update_bits(dev->regmap, 0x061, 0xc0, u8tmp); + if (dev->slave_ts) + ret = regmap_update_bits(dev->regmap, 0x021, 0xc0, u8tmp); + else + ret = regmap_update_bits(dev->regmap, 0x061, 0xc0, u8tmp); if (ret) goto err; @@ -986,8 +991,8 @@ static int rtl2832_pid_filter(struct dvb_frontend *fe, u8 index, u16 pid, int ret; u8 buf[4]; - dev_dbg(&client->dev, "index=%d pid=%04x onoff=%d\n", - index, pid, onoff); + dev_dbg(&client->dev, "index=%d pid=%04x onoff=%d slave_ts=%d\n", + index, pid, onoff, dev->slave_ts); /* skip invalid PIDs (0x2000) */ if (pid > 0x1fff || index > 32) @@ -1003,14 +1008,22 @@ static int rtl2832_pid_filter(struct dvb_frontend *fe, u8 index, u16 pid, buf[1] = (dev->filters >> 8) & 0xff; buf[2] = (dev->filters >> 16) & 0xff; buf[3] = (dev->filters >> 24) & 0xff; - ret = regmap_bulk_write(dev->regmap, 0x062, buf, 4); + + if (dev->slave_ts) + ret = regmap_bulk_write(dev->regmap, 0x022, buf, 4); + else + ret = regmap_bulk_write(dev->regmap, 0x062, buf, 4); if (ret) goto err; /* add PID */ buf[0] = (pid >> 8) & 0xff; buf[1] = (pid >> 0) & 0xff; - ret = regmap_bulk_write(dev->regmap, 0x066 + 2 * index, buf, 2); + + if (dev->slave_ts) + ret = regmap_bulk_write(dev->regmap, 0x026 + 2 * index, buf, 2); + else + ret = regmap_bulk_write(dev->regmap, 0x066 + 2 * index, buf, 2); if (ret) goto err; diff --git a/drivers/media/dvb-frontends/rtl2832_priv.h b/drivers/media/dvb-frontends/rtl2832_priv.h index c1a8a69e9015..9a6d01a9c690 100644 --- a/drivers/media/dvb-frontends/rtl2832_priv.h +++ b/drivers/media/dvb-frontends/rtl2832_priv.h @@ -44,6 +44,7 @@ struct rtl2832_dev { bool sleeping; struct delayed_work i2c_gate_work; unsigned long filters; /* PID filter */ + bool slave_ts; }; struct rtl2832_reg_entry { diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 993dc50c12db..ce9006e10a30 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 MEDIA_CEC_EDID ---help--- Support for the Analog Devices ADV7604 video decoder. @@ -218,10 +219,18 @@ config VIDEO_ADV7604 To compile this driver as a module, choose M here: the module will be called adv7604. +config VIDEO_ADV7604_CEC + bool "Enable Analog Devices ADV7604 CEC support" + depends on VIDEO_ADV7604 && MEDIA_CEC + ---help--- + When selected the adv7604 will support the optional + HDMI CEC feature. + config VIDEO_ADV7842 tristate "Analog Devices ADV7842 decoder" depends on VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API select HDMI + select MEDIA_CEC_EDID ---help--- Support for the Analog Devices ADV7842 video decoder. @@ -231,6 +240,13 @@ config VIDEO_ADV7842 To compile this driver as a module, choose M here: the module will be called adv7842. +config VIDEO_ADV7842_CEC + bool "Enable Analog Devices ADV7842 CEC support" + depends on VIDEO_ADV7842 && MEDIA_CEC + ---help--- + When selected the adv7842 will support the optional + HDMI CEC feature. + config VIDEO_BT819 tristate "BT819A VideoStream decoder" depends on VIDEO_V4L2 && I2C @@ -447,6 +463,7 @@ config VIDEO_ADV7511 tristate "Analog Devices ADV7511 encoder" depends on VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API select HDMI + select MEDIA_CEC_EDID ---help--- Support for the Analog Devices ADV7511 video encoder. @@ -455,6 +472,13 @@ config VIDEO_ADV7511 To compile this driver as a module, choose M here: the module will be called adv7511. +config VIDEO_ADV7511_CEC + bool "Enable Analog Devices ADV7511 CEC support" + depends on VIDEO_ADV7511 && MEDIA_CEC + ---help--- + When selected the adv7511 will support the optional + HDMI CEC feature. + config VIDEO_AD9389B tristate "Analog Devices AD9389B encoder" depends on VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API diff --git a/drivers/media/i2c/adv7511.c b/drivers/media/i2c/adv7511.c index 39271c35da48..39d409f99628 100644 --- a/drivers/media/i2c/adv7511.c +++ b/drivers/media/i2c/adv7511.c @@ -33,6 +33,7 @@ #include <media/v4l2-ctrls.h> #include <media/v4l2-dv-timings.h> #include <media/i2c/adv7511.h> +#include <media/cec.h> static int debug; module_param(debug, int, 0644); @@ -59,6 +60,8 @@ MODULE_LICENSE("GPL v2"); #define ADV7511_MIN_PIXELCLOCK 20000000 #define ADV7511_MAX_PIXELCLOCK 225000000 +#define ADV7511_MAX_ADDRS (3) + /* ********************************************************************** * @@ -90,12 +93,20 @@ struct adv7511_state { struct v4l2_ctrl_handler hdl; int chip_revision; u8 i2c_edid_addr; - u8 i2c_cec_addr; u8 i2c_pktmem_addr; + u8 i2c_cec_addr; + + struct i2c_client *i2c_cec; + struct cec_adapter *cec_adap; + u8 cec_addr[ADV7511_MAX_ADDRS]; + u8 cec_valid_addrs; + bool cec_enabled_adap; + /* Is the adv7511 powered on? */ bool power_on; /* Did we receive hotplug and rx-sense signals? */ bool have_monitor; + bool enabled_irq; /* timings from s_dv_timings */ struct v4l2_dv_timings dv_timings; u32 fmt_code; @@ -227,7 +238,7 @@ static int adv_smbus_read_i2c_block_data(struct i2c_client *client, return ret; } -static inline void adv7511_edid_rd(struct v4l2_subdev *sd, u16 len, u8 *buf) +static void adv7511_edid_rd(struct v4l2_subdev *sd, uint16_t len, uint8_t *buf) { struct adv7511_state *state = get_adv7511_state(sd); int i; @@ -242,6 +253,34 @@ static inline void adv7511_edid_rd(struct v4l2_subdev *sd, u16 len, u8 *buf) v4l2_err(sd, "%s: i2c read error\n", __func__); } +static inline int adv7511_cec_read(struct v4l2_subdev *sd, u8 reg) +{ + struct adv7511_state *state = get_adv7511_state(sd); + + return i2c_smbus_read_byte_data(state->i2c_cec, reg); +} + +static int adv7511_cec_write(struct v4l2_subdev *sd, u8 reg, u8 val) +{ + struct adv7511_state *state = get_adv7511_state(sd); + int ret; + int i; + + for (i = 0; i < 3; i++) { + ret = i2c_smbus_write_byte_data(state->i2c_cec, reg, val); + if (ret == 0) + return 0; + } + v4l2_err(sd, "%s: I2C Write Problem\n", __func__); + return ret; +} + +static inline int adv7511_cec_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, + u8 val) +{ + return adv7511_cec_write(sd, reg, (adv7511_cec_read(sd, reg) & mask) | val); +} + static int adv7511_pktmem_rd(struct v4l2_subdev *sd, u8 reg) { struct adv7511_state *state = get_adv7511_state(sd); @@ -425,16 +464,28 @@ static const struct v4l2_ctrl_ops adv7511_ctrl_ops = { #ifdef CONFIG_VIDEO_ADV_DEBUG static void adv7511_inv_register(struct v4l2_subdev *sd) { + struct adv7511_state *state = get_adv7511_state(sd); + v4l2_info(sd, "0x000-0x0ff: Main Map\n"); + if (state->i2c_cec) + v4l2_info(sd, "0x100-0x1ff: CEC Map\n"); } static int adv7511_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) { + struct adv7511_state *state = get_adv7511_state(sd); + reg->size = 1; switch (reg->reg >> 8) { case 0: reg->val = adv7511_rd(sd, reg->reg & 0xff); break; + case 1: + if (state->i2c_cec) { + reg->val = adv7511_cec_read(sd, reg->reg & 0xff); + break; + } + /* fall through */ default: v4l2_info(sd, "Register %03llx not supported\n", reg->reg); adv7511_inv_register(sd); @@ -445,10 +496,18 @@ static int adv7511_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register * static int adv7511_s_register(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg) { + struct adv7511_state *state = get_adv7511_state(sd); + switch (reg->reg >> 8) { case 0: adv7511_wr(sd, reg->reg & 0xff, reg->val & 0xff); break; + case 1: + if (state->i2c_cec) { + adv7511_cec_write(sd, reg->reg & 0xff, reg->val & 0xff); + break; + } + /* fall through */ default: v4l2_info(sd, "Register %03llx not supported\n", reg->reg); adv7511_inv_register(sd); @@ -536,6 +595,7 @@ static int adv7511_log_status(struct v4l2_subdev *sd) { struct adv7511_state *state = get_adv7511_state(sd); struct adv7511_state_edid *edid = &state->edid; + int i; static const char * const states[] = { "in reset", @@ -605,7 +665,23 @@ static int adv7511_log_status(struct v4l2_subdev *sd) else v4l2_info(sd, "no timings set\n"); v4l2_info(sd, "i2c edid addr: 0x%x\n", state->i2c_edid_addr); + + if (state->i2c_cec == NULL) + return 0; + v4l2_info(sd, "i2c cec addr: 0x%x\n", state->i2c_cec_addr); + + v4l2_info(sd, "CEC: %s\n", state->cec_enabled_adap ? + "enabled" : "disabled"); + if (state->cec_enabled_adap) { + for (i = 0; i < ADV7511_MAX_ADDRS; i++) { + bool is_valid = state->cec_valid_addrs & (1 << i); + + if (is_valid) + v4l2_info(sd, "CEC Logical Address: 0x%x\n", + state->cec_addr[i]); + } + } v4l2_info(sd, "i2c pktmem addr: 0x%x\n", state->i2c_pktmem_addr); return 0; } @@ -663,15 +739,197 @@ static int adv7511_s_power(struct v4l2_subdev *sd, int on) return true; } +#if IS_ENABLED(CONFIG_VIDEO_ADV7511_CEC) +static int adv7511_cec_adap_enable(struct cec_adapter *adap, bool enable) +{ + struct adv7511_state *state = adap->priv; + struct v4l2_subdev *sd = &state->sd; + + if (state->i2c_cec == NULL) + return -EIO; + + if (!state->cec_enabled_adap && enable) { + /* power up cec section */ + adv7511_cec_write_and_or(sd, 0x4e, 0xfc, 0x01); + /* legacy mode and clear all rx buffers */ + adv7511_cec_write(sd, 0x4a, 0x07); + adv7511_cec_write(sd, 0x4a, 0); + adv7511_cec_write_and_or(sd, 0x11, 0xfe, 0); /* initially disable tx */ + /* enabled irqs: */ + /* tx: ready */ + /* tx: arbitration lost */ + /* tx: retry timeout */ + /* rx: ready 1 */ + if (state->enabled_irq) + adv7511_wr_and_or(sd, 0x95, 0xc0, 0x39); + } else if (state->cec_enabled_adap && !enable) { + if (state->enabled_irq) + adv7511_wr_and_or(sd, 0x95, 0xc0, 0x00); + /* disable address mask 1-3 */ + adv7511_cec_write_and_or(sd, 0x4b, 0x8f, 0x00); + /* power down cec section */ + adv7511_cec_write_and_or(sd, 0x4e, 0xfc, 0x00); + state->cec_valid_addrs = 0; + } + state->cec_enabled_adap = enable; + return 0; +} + +static int adv7511_cec_adap_log_addr(struct cec_adapter *adap, u8 addr) +{ + struct adv7511_state *state = adap->priv; + struct v4l2_subdev *sd = &state->sd; + unsigned int i, free_idx = ADV7511_MAX_ADDRS; + + if (!state->cec_enabled_adap) + return addr == CEC_LOG_ADDR_INVALID ? 0 : -EIO; + + if (addr == CEC_LOG_ADDR_INVALID) { + adv7511_cec_write_and_or(sd, 0x4b, 0x8f, 0); + state->cec_valid_addrs = 0; + return 0; + } + + for (i = 0; i < ADV7511_MAX_ADDRS; i++) { + bool is_valid = state->cec_valid_addrs & (1 << i); + + if (free_idx == ADV7511_MAX_ADDRS && !is_valid) + free_idx = i; + if (is_valid && state->cec_addr[i] == addr) + return 0; + } + if (i == ADV7511_MAX_ADDRS) { + i = free_idx; + if (i == ADV7511_MAX_ADDRS) + return -ENXIO; + } + state->cec_addr[i] = addr; + state->cec_valid_addrs |= 1 << i; + + switch (i) { + case 0: + /* enable address mask 0 */ + adv7511_cec_write_and_or(sd, 0x4b, 0xef, 0x10); + /* set address for mask 0 */ + adv7511_cec_write_and_or(sd, 0x4c, 0xf0, addr); + break; + case 1: + /* enable address mask 1 */ + adv7511_cec_write_and_or(sd, 0x4b, 0xdf, 0x20); + /* set address for mask 1 */ + adv7511_cec_write_and_or(sd, 0x4c, 0x0f, addr << 4); + break; + case 2: + /* enable address mask 2 */ + adv7511_cec_write_and_or(sd, 0x4b, 0xbf, 0x40); + /* set address for mask 1 */ + adv7511_cec_write_and_or(sd, 0x4d, 0xf0, addr); + break; + } + return 0; +} + +static int adv7511_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, + u32 signal_free_time, struct cec_msg *msg) +{ + struct adv7511_state *state = adap->priv; + struct v4l2_subdev *sd = &state->sd; + u8 len = msg->len; + unsigned int i; + + v4l2_dbg(1, debug, sd, "%s: len %d\n", __func__, len); + + if (len > 16) { + v4l2_err(sd, "%s: len exceeded 16 (%d)\n", __func__, len); + return -EINVAL; + } + + /* + * The number of retries is the number of attempts - 1, but retry + * at least once. It's not clear if a value of 0 is allowed, so + * let's do at least one retry. + */ + adv7511_cec_write_and_or(sd, 0x12, ~0x70, max(1, attempts - 1) << 4); + + /* blocking, clear cec tx irq status */ + adv7511_wr_and_or(sd, 0x97, 0xc7, 0x38); + + /* write data */ + for (i = 0; i < len; i++) + adv7511_cec_write(sd, i, msg->msg[i]); + + /* set length (data + header) */ + adv7511_cec_write(sd, 0x10, len); + /* start transmit, enable tx */ + adv7511_cec_write(sd, 0x11, 0x01); + return 0; +} + +static void adv_cec_tx_raw_status(struct v4l2_subdev *sd, u8 tx_raw_status) +{ + struct adv7511_state *state = get_adv7511_state(sd); + + if ((adv7511_cec_read(sd, 0x11) & 0x01) == 0) { + v4l2_dbg(1, debug, sd, "%s: tx raw: tx disabled\n", __func__); + return; + } + + if (tx_raw_status & 0x10) { + v4l2_dbg(1, debug, sd, + "%s: tx raw: arbitration lost\n", __func__); + cec_transmit_done(state->cec_adap, CEC_TX_STATUS_ARB_LOST, + 1, 0, 0, 0); + return; + } + if (tx_raw_status & 0x08) { + u8 status; + u8 nack_cnt; + u8 low_drive_cnt; + + v4l2_dbg(1, debug, sd, "%s: tx raw: retry failed\n", __func__); + /* + * We set this status bit since this hardware performs + * retransmissions. + */ + status = CEC_TX_STATUS_MAX_RETRIES; + nack_cnt = adv7511_cec_read(sd, 0x14) & 0xf; + if (nack_cnt) + status |= CEC_TX_STATUS_NACK; + low_drive_cnt = adv7511_cec_read(sd, 0x14) >> 4; + if (low_drive_cnt) + status |= CEC_TX_STATUS_LOW_DRIVE; + cec_transmit_done(state->cec_adap, status, + 0, nack_cnt, low_drive_cnt, 0); + return; + } + if (tx_raw_status & 0x20) { + v4l2_dbg(1, debug, sd, "%s: tx raw: ready ok\n", __func__); + cec_transmit_done(state->cec_adap, CEC_TX_STATUS_OK, 0, 0, 0, 0); + return; + } +} + +static const struct cec_adap_ops adv7511_cec_adap_ops = { + .adap_enable = adv7511_cec_adap_enable, + .adap_log_addr = adv7511_cec_adap_log_addr, + .adap_transmit = adv7511_cec_adap_transmit, +}; +#endif + /* Enable interrupts */ static void adv7511_set_isr(struct v4l2_subdev *sd, bool enable) { + struct adv7511_state *state = get_adv7511_state(sd); u8 irqs = MASK_ADV7511_HPD_INT | MASK_ADV7511_MSEN_INT; u8 irqs_rd; int retries = 100; v4l2_dbg(2, debug, sd, "%s: %s\n", __func__, enable ? "enable" : "disable"); + if (state->enabled_irq == enable) + return; + state->enabled_irq = enable; + /* The datasheet says that the EDID ready interrupt should be disabled if there is no hotplug. */ if (!enable) @@ -679,6 +937,9 @@ static void adv7511_set_isr(struct v4l2_subdev *sd, bool enable) else if (adv7511_have_hotplug(sd)) irqs |= MASK_ADV7511_EDID_RDY_INT; + adv7511_wr_and_or(sd, 0x95, 0xc0, + (state->cec_enabled_adap && enable) ? 0x39 : 0x00); + /* * This i2c write can fail (approx. 1 in 1000 writes). But it * is essential that this register is correct, so retry it @@ -701,20 +962,53 @@ static void adv7511_set_isr(struct v4l2_subdev *sd, bool enable) static int adv7511_isr(struct v4l2_subdev *sd, u32 status, bool *handled) { u8 irq_status; + u8 cec_irq; /* disable interrupts to prevent a race condition */ adv7511_set_isr(sd, false); irq_status = adv7511_rd(sd, 0x96); + cec_irq = adv7511_rd(sd, 0x97); /* clear detected interrupts */ adv7511_wr(sd, 0x96, irq_status); + adv7511_wr(sd, 0x97, cec_irq); - v4l2_dbg(1, debug, sd, "%s: irq 0x%x\n", __func__, irq_status); + v4l2_dbg(1, debug, sd, "%s: irq 0x%x, cec-irq 0x%x\n", __func__, + irq_status, cec_irq); if (irq_status & (MASK_ADV7511_HPD_INT | MASK_ADV7511_MSEN_INT)) adv7511_check_monitor_present_status(sd); if (irq_status & MASK_ADV7511_EDID_RDY_INT) adv7511_check_edid_status(sd); +#if IS_ENABLED(CONFIG_VIDEO_ADV7511_CEC) + if (cec_irq & 0x38) + adv_cec_tx_raw_status(sd, cec_irq); + + if (cec_irq & 1) { + struct adv7511_state *state = get_adv7511_state(sd); + struct cec_msg msg; + + msg.len = adv7511_cec_read(sd, 0x25) & 0x1f; + + v4l2_dbg(1, debug, sd, "%s: cec msg len %d\n", __func__, + msg.len); + + if (msg.len > 16) + msg.len = 16; + + if (msg.len) { + u8 i; + + for (i = 0; i < msg.len; i++) + msg.msg[i] = adv7511_cec_read(sd, i + 0x15); + + adv7511_cec_write(sd, 0x4a, 1); /* toggle to re-enable rx 1 */ + adv7511_cec_write(sd, 0x4a, 0); + cec_received_msg(state->cec_adap, &msg); + } + } +#endif + /* enable interrupts */ adv7511_set_isr(sd, true); @@ -1183,6 +1477,8 @@ static void adv7511_notify_no_edid(struct v4l2_subdev *sd) /* We failed to read the EDID, so send an event for this. */ ed.present = false; ed.segment = adv7511_rd(sd, 0xc4); + ed.phys_addr = CEC_PHYS_ADDR_INVALID; + cec_s_phys_addr(state->cec_adap, ed.phys_addr, false); v4l2_subdev_notify(sd, ADV7511_EDID_DETECT, (void *)&ed); v4l2_ctrl_s_ctrl(state->have_edid0_ctrl, 0x0); } @@ -1406,13 +1702,16 @@ static bool adv7511_check_edid_status(struct v4l2_subdev *sd) v4l2_dbg(1, debug, sd, "%s: edid complete with %d segment(s)\n", __func__, state->edid.segments); state->edid.complete = true; - + ed.phys_addr = cec_get_edid_phys_addr(state->edid.data, + state->edid.segments * 256, + NULL); /* report when we have all segments but report only for segment 0 */ ed.present = true; ed.segment = 0; state->edid_detect_counter++; + cec_s_phys_addr(state->cec_adap, ed.phys_addr, false); v4l2_subdev_notify(sd, ADV7511_EDID_DETECT, (void *)&ed); return ed.present; } @@ -1420,17 +1719,43 @@ static bool adv7511_check_edid_status(struct v4l2_subdev *sd) return false; } +static int adv7511_registered(struct v4l2_subdev *sd) +{ + struct adv7511_state *state = get_adv7511_state(sd); + int err; + + err = cec_register_adapter(state->cec_adap); + if (err) + cec_delete_adapter(state->cec_adap); + return err; +} + +static void adv7511_unregistered(struct v4l2_subdev *sd) +{ + struct adv7511_state *state = get_adv7511_state(sd); + + cec_unregister_adapter(state->cec_adap); +} + +static const struct v4l2_subdev_internal_ops adv7511_int_ops = { + .registered = adv7511_registered, + .unregistered = adv7511_unregistered, +}; + /* ----------------------------------------------------------------------- */ /* Setup ADV7511 */ static void adv7511_init_setup(struct v4l2_subdev *sd) { struct adv7511_state *state = get_adv7511_state(sd); struct adv7511_state_edid *edid = &state->edid; + u32 cec_clk = state->pdata.cec_clk; + u8 ratio; v4l2_dbg(1, debug, sd, "%s\n", __func__); /* clear all interrupts */ adv7511_wr(sd, 0x96, 0xff); + adv7511_wr(sd, 0x97, 0xff); /* * Stop HPD from resetting a lot of registers. * It might leave the chip in a partly un-initialized state, @@ -1442,6 +1767,25 @@ static void adv7511_init_setup(struct v4l2_subdev *sd) adv7511_set_isr(sd, false); adv7511_s_stream(sd, false); adv7511_s_audio_stream(sd, false); + + if (state->i2c_cec == NULL) + return; + + v4l2_dbg(1, debug, sd, "%s: cec_clk %d\n", __func__, cec_clk); + + /* cec soft reset */ + adv7511_cec_write(sd, 0x50, 0x01); + adv7511_cec_write(sd, 0x50, 0x00); + + /* legacy mode */ + adv7511_cec_write(sd, 0x4a, 0x00); + + if (cec_clk % 750000 != 0) + v4l2_err(sd, "%s: cec_clk %d, not multiple of 750 Khz\n", + __func__, cec_clk); + + ratio = (cec_clk / 750000) - 1; + adv7511_cec_write(sd, 0x4e, ratio << 2); } static int adv7511_probe(struct i2c_client *client, const struct i2c_device_id *id) @@ -1476,6 +1820,7 @@ static int adv7511_probe(struct i2c_client *client, const struct i2c_device_id * client->addr << 1); v4l2_i2c_subdev_init(sd, client, &adv7511_ops); + sd->internal_ops = &adv7511_int_ops; hdl = &state->hdl; v4l2_ctrl_handler_init(hdl, 10); @@ -1516,26 +1861,47 @@ static int adv7511_probe(struct i2c_client *client, const struct i2c_device_id * chip_id[0] = adv7511_rd(sd, 0xf5); chip_id[1] = adv7511_rd(sd, 0xf6); if (chip_id[0] != 0x75 || chip_id[1] != 0x11) { - v4l2_err(sd, "chip_id != 0x7511, read 0x%02x%02x\n", chip_id[0], chip_id[1]); + v4l2_err(sd, "chip_id != 0x7511, read 0x%02x%02x\n", chip_id[0], + chip_id[1]); err = -EIO; goto err_entity; } - state->i2c_edid = i2c_new_dummy(client->adapter, state->i2c_edid_addr >> 1); + state->i2c_edid = i2c_new_dummy(client->adapter, + state->i2c_edid_addr >> 1); if (state->i2c_edid == NULL) { v4l2_err(sd, "failed to register edid i2c client\n"); err = -ENOMEM; goto err_entity; } + adv7511_wr(sd, 0xe1, state->i2c_cec_addr); + if (state->pdata.cec_clk < 3000000 || + state->pdata.cec_clk > 100000000) { + v4l2_err(sd, "%s: cec_clk %u outside range, disabling cec\n", + __func__, state->pdata.cec_clk); + state->pdata.cec_clk = 0; + } + + if (state->pdata.cec_clk) { + state->i2c_cec = i2c_new_dummy(client->adapter, + state->i2c_cec_addr >> 1); + if (state->i2c_cec == NULL) { + v4l2_err(sd, "failed to register cec i2c client\n"); + goto err_unreg_edid; + } + adv7511_wr(sd, 0xe2, 0x00); /* power up cec section */ + } else { + adv7511_wr(sd, 0xe2, 0x01); /* power down cec section */ + } + state->i2c_pktmem = i2c_new_dummy(client->adapter, state->i2c_pktmem_addr >> 1); if (state->i2c_pktmem == NULL) { v4l2_err(sd, "failed to register pktmem i2c client\n"); err = -ENOMEM; - goto err_unreg_edid; + goto err_unreg_cec; } - adv7511_wr(sd, 0xe2, 0x01); /* power down cec section */ state->work_queue = create_singlethread_workqueue(sd->name); if (state->work_queue == NULL) { v4l2_err(sd, "could not create workqueue\n"); @@ -1546,6 +1912,19 @@ static int adv7511_probe(struct i2c_client *client, const struct i2c_device_id * INIT_DELAYED_WORK(&state->edid_handler, adv7511_edid_handler); adv7511_init_setup(sd); + +#if IS_ENABLED(CONFIG_VIDEO_ADV7511_CEC) + state->cec_adap = cec_allocate_adapter(&adv7511_cec_adap_ops, + state, dev_name(&client->dev), CEC_CAP_TRANSMIT | + CEC_CAP_LOG_ADDRS | CEC_CAP_PASSTHROUGH | CEC_CAP_RC, + ADV7511_MAX_ADDRS, &client->dev); + err = PTR_ERR_OR_ZERO(state->cec_adap); + if (err) { + destroy_workqueue(state->work_queue); + goto err_unreg_pktmem; + } +#endif + adv7511_set_isr(sd, true); adv7511_check_monitor_present_status(sd); @@ -1555,6 +1934,9 @@ static int adv7511_probe(struct i2c_client *client, const struct i2c_device_id * err_unreg_pktmem: i2c_unregister_device(state->i2c_pktmem); +err_unreg_cec: + if (state->i2c_cec) + i2c_unregister_device(state->i2c_cec); err_unreg_edid: i2c_unregister_device(state->i2c_edid); err_entity: @@ -1576,9 +1958,12 @@ static int adv7511_remove(struct i2c_client *client) v4l2_dbg(1, debug, sd, "%s removed @ 0x%x (%s)\n", client->name, client->addr << 1, client->adapter->name); + adv7511_set_isr(sd, false); adv7511_init_setup(sd); cancel_delayed_work(&state->edid_handler); i2c_unregister_device(state->i2c_edid); + if (state->i2c_cec) + i2c_unregister_device(state->i2c_cec); i2c_unregister_device(state->i2c_pktmem); destroy_workqueue(state->work_queue); v4l2_device_unregister_subdev(sd); diff --git a/drivers/media/i2c/adv7604.c b/drivers/media/i2c/adv7604.c index beb2841ceae5..4abff32dc852 100644 --- a/drivers/media/i2c/adv7604.c +++ b/drivers/media/i2c/adv7604.c @@ -40,6 +40,7 @@ #include <linux/regmap.h> #include <media/i2c/adv7604.h> +#include <media/cec.h> #include <media/v4l2-ctrls.h> #include <media/v4l2-device.h> #include <media/v4l2-event.h> @@ -80,6 +81,8 @@ MODULE_LICENSE("GPL"); #define ADV76XX_OP_SWAP_CB_CR (1 << 0) +#define ADV76XX_MAX_ADDRS (3) + enum adv76xx_type { ADV7604, ADV7611, @@ -184,6 +187,12 @@ struct adv76xx_state { u16 spa_port_a[2]; struct v4l2_fract aspect_ratio; u32 rgb_quantization_range; + + struct cec_adapter *cec_adap; + u8 cec_addr[ADV76XX_MAX_ADDRS]; + u8 cec_valid_addrs; + bool cec_enabled_adap; + struct workqueue_struct *work_queues; struct delayed_work delayed_work_enable_hotplug; bool restart_stdi_once; @@ -381,7 +390,8 @@ static inline int io_write(struct v4l2_subdev *sd, u8 reg, u8 val) return regmap_write(state->regmap[ADV76XX_PAGE_IO], reg, val); } -static inline int io_write_clr_set(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +static inline int io_write_clr_set(struct v4l2_subdev *sd, u8 reg, u8 mask, + u8 val) { return io_write(sd, reg, (io_read(sd, reg) & ~mask) | val); } @@ -414,6 +424,12 @@ static inline int cec_write(struct v4l2_subdev *sd, u8 reg, u8 val) return regmap_write(state->regmap[ADV76XX_PAGE_CEC], reg, val); } +static inline int cec_write_clr_set(struct v4l2_subdev *sd, u8 reg, u8 mask, + u8 val) +{ + return cec_write(sd, reg, (cec_read(sd, reg) & ~mask) | val); +} + static inline int infoframe_read(struct v4l2_subdev *sd, u8 reg) { struct adv76xx_state *state = to_state(sd); @@ -779,11 +795,31 @@ static const struct v4l2_dv_timings_cap adv76xx_timings_cap_digital = { V4L2_DV_BT_CAP_CUSTOM) }; -static inline const struct v4l2_dv_timings_cap * -adv76xx_get_dv_timings_cap(struct v4l2_subdev *sd) +/* + * Return the DV timings capabilities for the requested sink pad. As a special + * case, pad value -1 returns the capabilities for the currently selected input. + */ +static const struct v4l2_dv_timings_cap * +adv76xx_get_dv_timings_cap(struct v4l2_subdev *sd, int pad) { - return is_digital_input(sd) ? &adv76xx_timings_cap_digital : - &adv7604_timings_cap_analog; + if (pad == -1) { + struct adv76xx_state *state = to_state(sd); + + pad = state->selected_input; + } + + switch (pad) { + case ADV76XX_PAD_HDMI_PORT_A: + case ADV7604_PAD_HDMI_PORT_B: + case ADV7604_PAD_HDMI_PORT_C: + case ADV7604_PAD_HDMI_PORT_D: + return &adv76xx_timings_cap_digital; + + case ADV7604_PAD_VGA_RGB: + case ADV7604_PAD_VGA_COMP: + default: + return &adv7604_timings_cap_analog; + } } @@ -872,9 +908,9 @@ static int adv76xx_s_detect_tx_5v_ctrl(struct v4l2_subdev *sd) { struct adv76xx_state *state = to_state(sd); const struct adv76xx_chip_info *info = state->info; + u16 cable_det = info->read_cable_det(sd); - return v4l2_ctrl_s_ctrl(state->detect_tx_5v_ctrl, - info->read_cable_det(sd)); + return v4l2_ctrl_s_ctrl(state->detect_tx_5v_ctrl, cable_det); } static int find_and_set_predefined_video_timings(struct v4l2_subdev *sd, @@ -1329,7 +1365,7 @@ static int stdi2dv_timings(struct v4l2_subdev *sd, const struct v4l2_bt_timings *bt = &v4l2_dv_timings_presets[i].bt; if (!v4l2_valid_dv_timings(&v4l2_dv_timings_presets[i], - adv76xx_get_dv_timings_cap(sd), + adv76xx_get_dv_timings_cap(sd, -1), adv76xx_check_dv_timings, NULL)) continue; if (vtotal(bt) != stdi->lcf + 1) @@ -1430,18 +1466,22 @@ static int adv76xx_enum_dv_timings(struct v4l2_subdev *sd, return -EINVAL; return v4l2_enum_dv_timings_cap(timings, - adv76xx_get_dv_timings_cap(sd), adv76xx_check_dv_timings, NULL); + adv76xx_get_dv_timings_cap(sd, timings->pad), + adv76xx_check_dv_timings, NULL); } static int adv76xx_dv_timings_cap(struct v4l2_subdev *sd, struct v4l2_dv_timings_cap *cap) { struct adv76xx_state *state = to_state(sd); + unsigned int pad = cap->pad; if (cap->pad >= state->source_pad) return -EINVAL; - *cap = *adv76xx_get_dv_timings_cap(sd); + *cap = *adv76xx_get_dv_timings_cap(sd, pad); + cap->pad = pad; + return 0; } @@ -1450,9 +1490,9 @@ static int adv76xx_dv_timings_cap(struct v4l2_subdev *sd, static void adv76xx_fill_optional_dv_timings_fields(struct v4l2_subdev *sd, struct v4l2_dv_timings *timings) { - v4l2_find_dv_timings_cap(timings, adv76xx_get_dv_timings_cap(sd), - is_digital_input(sd) ? 250000 : 1000000, - adv76xx_check_dv_timings, NULL); + v4l2_find_dv_timings_cap(timings, adv76xx_get_dv_timings_cap(sd, -1), + is_digital_input(sd) ? 250000 : 1000000, + adv76xx_check_dv_timings, NULL); } static unsigned int adv7604_read_hdmi_pixelclock(struct v4l2_subdev *sd) @@ -1620,7 +1660,7 @@ static int adv76xx_s_dv_timings(struct v4l2_subdev *sd, bt = &timings->bt; - if (!v4l2_valid_dv_timings(timings, adv76xx_get_dv_timings_cap(sd), + if (!v4l2_valid_dv_timings(timings, adv76xx_get_dv_timings_cap(sd, -1), adv76xx_check_dv_timings, NULL)) return -ERANGE; @@ -1900,6 +1940,210 @@ static int adv76xx_set_format(struct v4l2_subdev *sd, return 0; } +#if IS_ENABLED(CONFIG_VIDEO_ADV7604_CEC) +static void adv76xx_cec_tx_raw_status(struct v4l2_subdev *sd, u8 tx_raw_status) +{ + struct adv76xx_state *state = to_state(sd); + + if ((cec_read(sd, 0x11) & 0x01) == 0) { + v4l2_dbg(1, debug, sd, "%s: tx raw: tx disabled\n", __func__); + return; + } + + if (tx_raw_status & 0x02) { + v4l2_dbg(1, debug, sd, "%s: tx raw: arbitration lost\n", + __func__); + cec_transmit_done(state->cec_adap, CEC_TX_STATUS_ARB_LOST, + 1, 0, 0, 0); + } + if (tx_raw_status & 0x04) { + u8 status; + u8 nack_cnt; + u8 low_drive_cnt; + + v4l2_dbg(1, debug, sd, "%s: tx raw: retry failed\n", __func__); + /* + * We set this status bit since this hardware performs + * retransmissions. + */ + status = CEC_TX_STATUS_MAX_RETRIES; + nack_cnt = cec_read(sd, 0x14) & 0xf; + if (nack_cnt) + status |= CEC_TX_STATUS_NACK; + low_drive_cnt = cec_read(sd, 0x14) >> 4; + if (low_drive_cnt) + status |= CEC_TX_STATUS_LOW_DRIVE; + cec_transmit_done(state->cec_adap, status, + 0, nack_cnt, low_drive_cnt, 0); + return; + } + if (tx_raw_status & 0x01) { + v4l2_dbg(1, debug, sd, "%s: tx raw: ready ok\n", __func__); + cec_transmit_done(state->cec_adap, CEC_TX_STATUS_OK, 0, 0, 0, 0); + return; + } +} + +static void adv76xx_cec_isr(struct v4l2_subdev *sd, bool *handled) +{ + struct adv76xx_state *state = to_state(sd); + u8 cec_irq; + + /* cec controller */ + cec_irq = io_read(sd, 0x4d) & 0x0f; + if (!cec_irq) + return; + + v4l2_dbg(1, debug, sd, "%s: cec: irq 0x%x\n", __func__, cec_irq); + adv76xx_cec_tx_raw_status(sd, cec_irq); + if (cec_irq & 0x08) { + struct cec_msg msg; + + msg.len = cec_read(sd, 0x25) & 0x1f; + if (msg.len > 16) + msg.len = 16; + + if (msg.len) { + u8 i; + + for (i = 0; i < msg.len; i++) + msg.msg[i] = cec_read(sd, i + 0x15); + cec_write(sd, 0x26, 0x01); /* re-enable rx */ + cec_received_msg(state->cec_adap, &msg); + } + } + + /* note: the bit order is swapped between 0x4d and 0x4e */ + cec_irq = ((cec_irq & 0x08) >> 3) | ((cec_irq & 0x04) >> 1) | + ((cec_irq & 0x02) << 1) | ((cec_irq & 0x01) << 3); + io_write(sd, 0x4e, cec_irq); + + if (handled) + *handled = true; +} + +static int adv76xx_cec_adap_enable(struct cec_adapter *adap, bool enable) +{ + struct adv76xx_state *state = adap->priv; + struct v4l2_subdev *sd = &state->sd; + + if (!state->cec_enabled_adap && enable) { + cec_write_clr_set(sd, 0x2a, 0x01, 0x01); /* power up cec */ + cec_write(sd, 0x2c, 0x01); /* cec soft reset */ + cec_write_clr_set(sd, 0x11, 0x01, 0); /* initially disable tx */ + /* enabled irqs: */ + /* tx: ready */ + /* tx: arbitration lost */ + /* tx: retry timeout */ + /* rx: ready */ + io_write_clr_set(sd, 0x50, 0x0f, 0x0f); + cec_write(sd, 0x26, 0x01); /* enable rx */ + } else if (state->cec_enabled_adap && !enable) { + /* disable cec interrupts */ + io_write_clr_set(sd, 0x50, 0x0f, 0x00); + /* disable address mask 1-3 */ + cec_write_clr_set(sd, 0x27, 0x70, 0x00); + /* power down cec section */ + cec_write_clr_set(sd, 0x2a, 0x01, 0x00); + state->cec_valid_addrs = 0; + } + state->cec_enabled_adap = enable; + adv76xx_s_detect_tx_5v_ctrl(sd); + return 0; +} + +static int adv76xx_cec_adap_log_addr(struct cec_adapter *adap, u8 addr) +{ + struct adv76xx_state *state = adap->priv; + struct v4l2_subdev *sd = &state->sd; + unsigned int i, free_idx = ADV76XX_MAX_ADDRS; + + if (!state->cec_enabled_adap) + return addr == CEC_LOG_ADDR_INVALID ? 0 : -EIO; + + if (addr == CEC_LOG_ADDR_INVALID) { + cec_write_clr_set(sd, 0x27, 0x70, 0); + state->cec_valid_addrs = 0; + return 0; + } + + for (i = 0; i < ADV76XX_MAX_ADDRS; i++) { + bool is_valid = state->cec_valid_addrs & (1 << i); + + if (free_idx == ADV76XX_MAX_ADDRS && !is_valid) + free_idx = i; + if (is_valid && state->cec_addr[i] == addr) + return 0; + } + if (i == ADV76XX_MAX_ADDRS) { + i = free_idx; + if (i == ADV76XX_MAX_ADDRS) + return -ENXIO; + } + state->cec_addr[i] = addr; + state->cec_valid_addrs |= 1 << i; + + switch (i) { + case 0: + /* enable address mask 0 */ + cec_write_clr_set(sd, 0x27, 0x10, 0x10); + /* set address for mask 0 */ + cec_write_clr_set(sd, 0x28, 0x0f, addr); + break; + case 1: + /* enable address mask 1 */ + cec_write_clr_set(sd, 0x27, 0x20, 0x20); + /* set address for mask 1 */ + cec_write_clr_set(sd, 0x28, 0xf0, addr << 4); + break; + case 2: + /* enable address mask 2 */ + cec_write_clr_set(sd, 0x27, 0x40, 0x40); + /* set address for mask 1 */ + cec_write_clr_set(sd, 0x29, 0x0f, addr); + break; + } + return 0; +} + +static int adv76xx_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, + u32 signal_free_time, struct cec_msg *msg) +{ + struct adv76xx_state *state = adap->priv; + struct v4l2_subdev *sd = &state->sd; + u8 len = msg->len; + unsigned int i; + + /* + * The number of retries is the number of attempts - 1, but retry + * at least once. It's not clear if a value of 0 is allowed, so + * let's do at least one retry. + */ + cec_write_clr_set(sd, 0x12, 0x70, max(1, attempts - 1) << 4); + + if (len > 16) { + v4l2_err(sd, "%s: len exceeded 16 (%d)\n", __func__, len); + return -EINVAL; + } + + /* write data */ + for (i = 0; i < len; i++) + cec_write(sd, i, msg->msg[i]); + + /* set length (data + header) */ + cec_write(sd, 0x10, len); + /* start transmit, enable tx */ + cec_write(sd, 0x11, 0x01); + return 0; +} + +static const struct cec_adap_ops adv76xx_cec_adap_ops = { + .adap_enable = adv76xx_cec_adap_enable, + .adap_log_addr = adv76xx_cec_adap_log_addr, + .adap_transmit = adv76xx_cec_adap_transmit, +}; +#endif + static int adv76xx_isr(struct v4l2_subdev *sd, u32 status, bool *handled) { struct adv76xx_state *state = to_state(sd); @@ -1945,6 +2189,11 @@ static int adv76xx_isr(struct v4l2_subdev *sd, u32 status, bool *handled) *handled = true; } +#if IS_ENABLED(CONFIG_VIDEO_ADV7604_CEC) + /* cec */ + adv76xx_cec_isr(sd, handled); +#endif + /* tx 5v detect */ tx_5v = irq_reg_0x70 & info->cable_det_mask; if (tx_5v) { @@ -1994,39 +2243,12 @@ static int adv76xx_get_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) return 0; } -static int get_edid_spa_location(const u8 *edid) -{ - u8 d; - - if ((edid[0x7e] != 1) || - (edid[0x80] != 0x02) || - (edid[0x81] != 0x03)) { - return -1; - } - - /* search Vendor Specific Data Block (tag 3) */ - d = edid[0x82] & 0x7f; - if (d > 4) { - int i = 0x84; - int end = 0x80 + d; - - do { - u8 tag = edid[i] >> 5; - u8 len = edid[i] & 0x1f; - - if ((tag == 3) && (len >= 5)) - return i + 4; - i += len + 1; - } while (i < end); - } - return -1; -} - static int adv76xx_set_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) { struct adv76xx_state *state = to_state(sd); const struct adv76xx_chip_info *info = state->info; - int spa_loc; + unsigned int spa_loc; + u16 pa; int err; int i; @@ -2057,6 +2279,10 @@ static int adv76xx_set_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) edid->blocks = 2; return -E2BIG; } + pa = cec_get_edid_phys_addr(edid->edid, edid->blocks * 128, &spa_loc); + err = cec_phys_addr_validate(pa, &pa, NULL); + if (err) + return err; v4l2_dbg(2, debug, sd, "%s: write EDID pad %d, edid.present = 0x%x\n", __func__, edid->pad, state->edid.present); @@ -2066,9 +2292,12 @@ static int adv76xx_set_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) adv76xx_set_hpd(state, 0); rep_write_clr_set(sd, info->edid_enable_reg, 0x0f, 0x00); - spa_loc = get_edid_spa_location(edid->edid); - if (spa_loc < 0) - spa_loc = 0xc0; /* Default value [REF_02, p. 116] */ + /* + * Return an error if no location of the source physical address + * was found. + */ + if (spa_loc == 0) + return -EINVAL; switch (edid->pad) { case ADV76XX_PAD_HDMI_PORT_A: @@ -2128,6 +2357,7 @@ static int adv76xx_set_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) v4l2_err(sd, "error enabling edid (0x%x)\n", state->edid.present); return -EIO; } + cec_s_phys_addr(state->cec_adap, pa, false); /* enable hotplug after 100 ms */ queue_delayed_work(state->work_queues, @@ -2252,8 +2482,19 @@ static int adv76xx_log_status(struct v4l2_subdev *sd) ((edid_enabled & 0x02) ? "Yes" : "No"), ((edid_enabled & 0x04) ? "Yes" : "No"), ((edid_enabled & 0x08) ? "Yes" : "No")); - v4l2_info(sd, "CEC: %s\n", !!(cec_read(sd, 0x2a) & 0x01) ? + v4l2_info(sd, "CEC: %s\n", state->cec_enabled_adap ? "enabled" : "disabled"); + if (state->cec_enabled_adap) { + int i; + + for (i = 0; i < ADV76XX_MAX_ADDRS; i++) { + bool is_valid = state->cec_valid_addrs & (1 << i); + + if (is_valid) + v4l2_info(sd, "CEC Logical Address: 0x%x\n", + state->cec_addr[i]); + } + } v4l2_info(sd, "-----Signal status-----\n"); cable_det = info->read_cable_det(sd); @@ -2363,6 +2604,24 @@ static int adv76xx_subscribe_event(struct v4l2_subdev *sd, } } +static int adv76xx_registered(struct v4l2_subdev *sd) +{ + struct adv76xx_state *state = to_state(sd); + int err; + + err = cec_register_adapter(state->cec_adap); + if (err) + cec_delete_adapter(state->cec_adap); + return err; +} + +static void adv76xx_unregistered(struct v4l2_subdev *sd) +{ + struct adv76xx_state *state = to_state(sd); + + cec_unregister_adapter(state->cec_adap); +} + /* ----------------------------------------------------------------------- */ static const struct v4l2_ctrl_ops adv76xx_ctrl_ops = { @@ -2406,6 +2665,11 @@ static const struct v4l2_subdev_ops adv76xx_ops = { .pad = &adv76xx_pad_ops, }; +static const struct v4l2_subdev_internal_ops adv76xx_int_ops = { + .registered = adv76xx_registered, + .unregistered = adv76xx_unregistered, +}; + /* -------------------------- custom ctrls ---------------------------------- */ static const struct v4l2_ctrl_config adv7604_ctrl_analog_sampling_phase = { @@ -3069,6 +3333,7 @@ static int adv76xx_probe(struct i2c_client *client, id->name, i2c_adapter_id(client->adapter), client->addr); sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; + sd->internal_ops = &adv76xx_int_ops; /* Configure IO Regmap region */ err = configure_regmap(state, ADV76XX_PAGE_IO); @@ -3212,6 +3477,18 @@ static int adv76xx_probe(struct i2c_client *client, err = adv76xx_core_init(sd); if (err) goto err_entity; + +#if IS_ENABLED(CONFIG_VIDEO_ADV7604_CEC) + state->cec_adap = cec_allocate_adapter(&adv76xx_cec_adap_ops, + state, dev_name(&client->dev), + CEC_CAP_TRANSMIT | CEC_CAP_LOG_ADDRS | + CEC_CAP_PASSTHROUGH | CEC_CAP_RC, ADV76XX_MAX_ADDRS, + &client->dev); + err = PTR_ERR_OR_ZERO(state->cec_adap); + if (err) + goto err_entity; +#endif + v4l2_info(sd, "%s found @ 0x%x (%s)\n", client->name, client->addr << 1, client->adapter->name); @@ -3240,6 +3517,13 @@ static int adv76xx_remove(struct i2c_client *client) struct v4l2_subdev *sd = i2c_get_clientdata(client); struct adv76xx_state *state = to_state(sd); + /* disable interrupts */ + io_write(sd, 0x40, 0); + io_write(sd, 0x41, 0); + io_write(sd, 0x46, 0); + io_write(sd, 0x6e, 0); + io_write(sd, 0x73, 0); + cancel_delayed_work(&state->delayed_work_enable_hotplug); destroy_workqueue(state->work_queues); v4l2_async_unregister_subdev(sd); diff --git a/drivers/media/i2c/adv7842.c b/drivers/media/i2c/adv7842.c index ecaacb0a6fa1..fb01fee33dfe 100644 --- a/drivers/media/i2c/adv7842.c +++ b/drivers/media/i2c/adv7842.c @@ -39,6 +39,7 @@ #include <linux/workqueue.h> #include <linux/v4l2-dv-timings.h> #include <linux/hdmi.h> +#include <media/cec.h> #include <media/v4l2-device.h> #include <media/v4l2-event.h> #include <media/v4l2-ctrls.h> @@ -79,6 +80,8 @@ MODULE_LICENSE("GPL"); #define ADV7842_OP_SWAP_CB_CR (1 << 0) +#define ADV7842_MAX_ADDRS (3) + /* ********************************************************************** * @@ -142,6 +145,11 @@ struct adv7842_state { struct v4l2_ctrl *free_run_color_ctrl_manual; struct v4l2_ctrl *free_run_color_ctrl; struct v4l2_ctrl *rgb_quantization_range_ctrl; + + struct cec_adapter *cec_adap; + u8 cec_addr[ADV7842_MAX_ADDRS]; + u8 cec_valid_addrs; + bool cec_enabled_adap; }; /* Unsupported timings. This device cannot support 720p30. */ @@ -418,9 +426,9 @@ static inline int cec_write(struct v4l2_subdev *sd, u8 reg, u8 val) return adv_smbus_write_byte_data(state->i2c_cec, reg, val); } -static inline int cec_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +static inline int cec_write_clr_set(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) { - return cec_write(sd, reg, (cec_read(sd, reg) & mask) | val); + return cec_write(sd, reg, (cec_read(sd, reg) & ~mask) | val); } static inline int infoframe_read(struct v4l2_subdev *sd, u8 reg) @@ -696,6 +704,18 @@ adv7842_get_dv_timings_cap(struct v4l2_subdev *sd) /* ----------------------------------------------------------------------- */ +static u16 adv7842_read_cable_det(struct v4l2_subdev *sd) +{ + u8 reg = io_read(sd, 0x6f); + u16 val = 0; + + if (reg & 0x02) + val |= 1; /* port A */ + if (reg & 0x01) + val |= 2; /* port B */ + return val; +} + static void adv7842_delayed_work_enable_hotplug(struct work_struct *work) { struct delayed_work *dwork = to_delayed_work(work); @@ -762,50 +782,18 @@ static int edid_write_vga_segment(struct v4l2_subdev *sd) return 0; } -static int edid_spa_location(const u8 *edid) -{ - u8 d; - - /* - * TODO, improve and update for other CEA extensions - * currently only for 1 segment (256 bytes), - * i.e. 1 extension block and CEA revision 3. - */ - if ((edid[0x7e] != 1) || - (edid[0x80] != 0x02) || - (edid[0x81] != 0x03)) { - return -EINVAL; - } - /* - * search Vendor Specific Data Block (tag 3) - */ - d = edid[0x82] & 0x7f; - if (d > 4) { - int i = 0x84; - int end = 0x80 + d; - do { - u8 tag = edid[i]>>5; - u8 len = edid[i] & 0x1f; - - if ((tag == 3) && (len >= 5)) - return i + 4; - i += len + 1; - } while (i < end); - } - return -EINVAL; -} - static int edid_write_hdmi_segment(struct v4l2_subdev *sd, u8 port) { struct i2c_client *client = v4l2_get_subdevdata(sd); struct adv7842_state *state = to_state(sd); - const u8 *val = state->hdmi_edid.edid; - int spa_loc = edid_spa_location(val); + const u8 *edid = state->hdmi_edid.edid; + int spa_loc; + u16 pa; int err = 0; int i; - v4l2_dbg(2, debug, sd, "%s: write EDID on port %c (spa at 0x%x)\n", - __func__, (port == ADV7842_EDID_PORT_A) ? 'A' : 'B', spa_loc); + v4l2_dbg(2, debug, sd, "%s: write EDID on port %c\n", + __func__, (port == ADV7842_EDID_PORT_A) ? 'A' : 'B'); /* HPA disable on port A and B */ io_write_and_or(sd, 0x20, 0xcf, 0x00); @@ -816,24 +804,33 @@ static int edid_write_hdmi_segment(struct v4l2_subdev *sd, u8 port) if (!state->hdmi_edid.present) return 0; + pa = cec_get_edid_phys_addr(edid, 256, &spa_loc); + err = cec_phys_addr_validate(pa, &pa, NULL); + if (err) + return err; + + /* + * Return an error if no location of the source physical address + * was found. + */ + if (spa_loc == 0) + return -EINVAL; + /* edid segment pointer '0' for HDMI ports */ rep_write_and_or(sd, 0x77, 0xef, 0x00); for (i = 0; !err && i < 256; i += I2C_SMBUS_BLOCK_MAX) err = adv_smbus_write_i2c_block_data(state->i2c_edid, i, - I2C_SMBUS_BLOCK_MAX, val + i); + I2C_SMBUS_BLOCK_MAX, edid + i); if (err) return err; - if (spa_loc < 0) - spa_loc = 0xc0; /* Default value [REF_02, p. 199] */ - if (port == ADV7842_EDID_PORT_A) { - rep_write(sd, 0x72, val[spa_loc]); - rep_write(sd, 0x73, val[spa_loc + 1]); + rep_write(sd, 0x72, edid[spa_loc]); + rep_write(sd, 0x73, edid[spa_loc + 1]); } else { - rep_write(sd, 0x74, val[spa_loc]); - rep_write(sd, 0x75, val[spa_loc + 1]); + rep_write(sd, 0x74, edid[spa_loc]); + rep_write(sd, 0x75, edid[spa_loc + 1]); } rep_write(sd, 0x76, spa_loc & 0xff); rep_write_and_or(sd, 0x77, 0xbf, (spa_loc >> 2) & 0x40); @@ -853,6 +850,7 @@ static int edid_write_hdmi_segment(struct v4l2_subdev *sd, u8 port) (port == ADV7842_EDID_PORT_A) ? 'A' : 'B'); return -EIO; } + cec_s_phys_addr(state->cec_adap, pa, false); /* enable hotplug after 200 ms */ queue_delayed_work(state->work_queues, @@ -983,20 +981,11 @@ static int adv7842_s_register(struct v4l2_subdev *sd, static int adv7842_s_detect_tx_5v_ctrl(struct v4l2_subdev *sd) { struct adv7842_state *state = to_state(sd); - int prev = v4l2_ctrl_g_ctrl(state->detect_tx_5v_ctrl); - u8 reg_io_6f = io_read(sd, 0x6f); - int val = 0; - - if (reg_io_6f & 0x02) - val |= 1; /* port A */ - if (reg_io_6f & 0x01) - val |= 2; /* port B */ + u16 cable_det = adv7842_read_cable_det(sd); - v4l2_dbg(1, debug, sd, "%s: 0x%x -> 0x%x\n", __func__, prev, val); + v4l2_dbg(1, debug, sd, "%s: 0x%x\n", __func__, cable_det); - if (val != prev) - return v4l2_ctrl_s_ctrl(state->detect_tx_5v_ctrl, val); - return 0; + return v4l2_ctrl_s_ctrl(state->detect_tx_5v_ctrl, cable_det); } static int find_and_set_predefined_video_timings(struct v4l2_subdev *sd, @@ -2170,6 +2159,207 @@ static void adv7842_irq_enable(struct v4l2_subdev *sd, bool enable) } } +#if IS_ENABLED(CONFIG_VIDEO_ADV7842_CEC) +static void adv7842_cec_tx_raw_status(struct v4l2_subdev *sd, u8 tx_raw_status) +{ + struct adv7842_state *state = to_state(sd); + + if ((cec_read(sd, 0x11) & 0x01) == 0) { + v4l2_dbg(1, debug, sd, "%s: tx raw: tx disabled\n", __func__); + return; + } + + if (tx_raw_status & 0x02) { + v4l2_dbg(1, debug, sd, "%s: tx raw: arbitration lost\n", + __func__); + cec_transmit_done(state->cec_adap, CEC_TX_STATUS_ARB_LOST, + 1, 0, 0, 0); + return; + } + if (tx_raw_status & 0x04) { + u8 status; + u8 nack_cnt; + u8 low_drive_cnt; + + v4l2_dbg(1, debug, sd, "%s: tx raw: retry failed\n", __func__); + /* + * We set this status bit since this hardware performs + * retransmissions. + */ + status = CEC_TX_STATUS_MAX_RETRIES; + nack_cnt = cec_read(sd, 0x14) & 0xf; + if (nack_cnt) + status |= CEC_TX_STATUS_NACK; + low_drive_cnt = cec_read(sd, 0x14) >> 4; + if (low_drive_cnt) + status |= CEC_TX_STATUS_LOW_DRIVE; + cec_transmit_done(state->cec_adap, status, + 0, nack_cnt, low_drive_cnt, 0); + return; + } + if (tx_raw_status & 0x01) { + v4l2_dbg(1, debug, sd, "%s: tx raw: ready ok\n", __func__); + cec_transmit_done(state->cec_adap, CEC_TX_STATUS_OK, 0, 0, 0, 0); + return; + } +} + +static void adv7842_cec_isr(struct v4l2_subdev *sd, bool *handled) +{ + u8 cec_irq; + + /* cec controller */ + cec_irq = io_read(sd, 0x93) & 0x0f; + if (!cec_irq) + return; + + v4l2_dbg(1, debug, sd, "%s: cec: irq 0x%x\n", __func__, cec_irq); + adv7842_cec_tx_raw_status(sd, cec_irq); + if (cec_irq & 0x08) { + struct adv7842_state *state = to_state(sd); + struct cec_msg msg; + + msg.len = cec_read(sd, 0x25) & 0x1f; + if (msg.len > 16) + msg.len = 16; + + if (msg.len) { + u8 i; + + for (i = 0; i < msg.len; i++) + msg.msg[i] = cec_read(sd, i + 0x15); + cec_write(sd, 0x26, 0x01); /* re-enable rx */ + cec_received_msg(state->cec_adap, &msg); + } + } + + io_write(sd, 0x94, cec_irq); + + if (handled) + *handled = true; +} + +static int adv7842_cec_adap_enable(struct cec_adapter *adap, bool enable) +{ + struct adv7842_state *state = adap->priv; + struct v4l2_subdev *sd = &state->sd; + + if (!state->cec_enabled_adap && enable) { + cec_write_clr_set(sd, 0x2a, 0x01, 0x01); /* power up cec */ + cec_write(sd, 0x2c, 0x01); /* cec soft reset */ + cec_write_clr_set(sd, 0x11, 0x01, 0); /* initially disable tx */ + /* enabled irqs: */ + /* tx: ready */ + /* tx: arbitration lost */ + /* tx: retry timeout */ + /* rx: ready */ + io_write_clr_set(sd, 0x96, 0x0f, 0x0f); + cec_write(sd, 0x26, 0x01); /* enable rx */ + } else if (state->cec_enabled_adap && !enable) { + /* disable cec interrupts */ + io_write_clr_set(sd, 0x96, 0x0f, 0x00); + /* disable address mask 1-3 */ + cec_write_clr_set(sd, 0x27, 0x70, 0x00); + /* power down cec section */ + cec_write_clr_set(sd, 0x2a, 0x01, 0x00); + state->cec_valid_addrs = 0; + } + state->cec_enabled_adap = enable; + return 0; +} + +static int adv7842_cec_adap_log_addr(struct cec_adapter *adap, u8 addr) +{ + struct adv7842_state *state = adap->priv; + struct v4l2_subdev *sd = &state->sd; + unsigned int i, free_idx = ADV7842_MAX_ADDRS; + + if (!state->cec_enabled_adap) + return addr == CEC_LOG_ADDR_INVALID ? 0 : -EIO; + + if (addr == CEC_LOG_ADDR_INVALID) { + cec_write_clr_set(sd, 0x27, 0x70, 0); + state->cec_valid_addrs = 0; + return 0; + } + + for (i = 0; i < ADV7842_MAX_ADDRS; i++) { + bool is_valid = state->cec_valid_addrs & (1 << i); + + if (free_idx == ADV7842_MAX_ADDRS && !is_valid) + free_idx = i; + if (is_valid && state->cec_addr[i] == addr) + return 0; + } + if (i == ADV7842_MAX_ADDRS) { + i = free_idx; + if (i == ADV7842_MAX_ADDRS) + return -ENXIO; + } + state->cec_addr[i] = addr; + state->cec_valid_addrs |= 1 << i; + + switch (i) { + case 0: + /* enable address mask 0 */ + cec_write_clr_set(sd, 0x27, 0x10, 0x10); + /* set address for mask 0 */ + cec_write_clr_set(sd, 0x28, 0x0f, addr); + break; + case 1: + /* enable address mask 1 */ + cec_write_clr_set(sd, 0x27, 0x20, 0x20); + /* set address for mask 1 */ + cec_write_clr_set(sd, 0x28, 0xf0, addr << 4); + break; + case 2: + /* enable address mask 2 */ + cec_write_clr_set(sd, 0x27, 0x40, 0x40); + /* set address for mask 1 */ + cec_write_clr_set(sd, 0x29, 0x0f, addr); + break; + } + return 0; +} + +static int adv7842_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, + u32 signal_free_time, struct cec_msg *msg) +{ + struct adv7842_state *state = adap->priv; + struct v4l2_subdev *sd = &state->sd; + u8 len = msg->len; + unsigned int i; + + /* + * The number of retries is the number of attempts - 1, but retry + * at least once. It's not clear if a value of 0 is allowed, so + * let's do at least one retry. + */ + cec_write_clr_set(sd, 0x12, 0x70, max(1, attempts - 1) << 4); + + if (len > 16) { + v4l2_err(sd, "%s: len exceeded 16 (%d)\n", __func__, len); + return -EINVAL; + } + + /* write data */ + for (i = 0; i < len; i++) + cec_write(sd, i, msg->msg[i]); + + /* set length (data + header) */ + cec_write(sd, 0x10, len); + /* start transmit, enable tx */ + cec_write(sd, 0x11, 0x01); + return 0; +} + +static const struct cec_adap_ops adv7842_cec_adap_ops = { + .adap_enable = adv7842_cec_adap_enable, + .adap_log_addr = adv7842_cec_adap_log_addr, + .adap_transmit = adv7842_cec_adap_transmit, +}; +#endif + static int adv7842_isr(struct v4l2_subdev *sd, u32 status, bool *handled) { struct adv7842_state *state = to_state(sd); @@ -2241,6 +2431,11 @@ static int adv7842_isr(struct v4l2_subdev *sd, u32 status, bool *handled) *handled = true; } +#if IS_ENABLED(CONFIG_VIDEO_ADV7842_CEC) + /* cec */ + adv7842_cec_isr(sd, handled); +#endif + /* tx 5v detect */ if (irq_status[2] & 0x3) { v4l2_dbg(1, debug, sd, "%s: irq tx_5v\n", __func__); @@ -2321,10 +2516,12 @@ static int adv7842_set_edid(struct v4l2_subdev *sd, struct v4l2_edid *e) case ADV7842_EDID_PORT_A: case ADV7842_EDID_PORT_B: memset(&state->hdmi_edid.edid, 0, 256); - if (e->blocks) + if (e->blocks) { state->hdmi_edid.present |= 0x04 << e->pad; - else + } else { state->hdmi_edid.present &= ~(0x04 << e->pad); + adv7842_s_detect_tx_5v_ctrl(sd); + } memcpy(&state->hdmi_edid.edid, e->edid, 128 * e->blocks); err = edid_write_hdmi_segment(sd, e->pad); break; @@ -2397,6 +2594,8 @@ static void adv7842_log_infoframes(struct v4l2_subdev *sd) log_infoframe(sd, &cri[i]); } +#if 0 +/* Let's keep it here for now, as it could be useful for debug */ static const char * const prim_mode_txt[] = { "SDP", "Component", @@ -2415,6 +2614,7 @@ static const char * const prim_mode_txt[] = { "Reserved", "Reserved", }; +#endif static int adv7842_sdp_log_status(struct v4l2_subdev *sd) { @@ -2509,8 +2709,19 @@ static int adv7842_cp_log_status(struct v4l2_subdev *sd) v4l2_info(sd, "HPD A %s, B %s\n", reg_io_0x21 & 0x02 ? "enabled" : "disabled", reg_io_0x21 & 0x01 ? "enabled" : "disabled"); - v4l2_info(sd, "CEC %s\n", !!(cec_read(sd, 0x2a) & 0x01) ? + v4l2_info(sd, "CEC: %s\n", state->cec_enabled_adap ? "enabled" : "disabled"); + if (state->cec_enabled_adap) { + int i; + + for (i = 0; i < ADV7842_MAX_ADDRS; i++) { + bool is_valid = state->cec_valid_addrs & (1 << i); + + if (is_valid) + v4l2_info(sd, "CEC Logical Address: 0x%x\n", + state->cec_addr[i]); + } + } v4l2_info(sd, "-----Signal status-----\n"); if (state->hdmi_port_a) { @@ -3031,6 +3242,24 @@ static int adv7842_subscribe_event(struct v4l2_subdev *sd, } } +static int adv7842_registered(struct v4l2_subdev *sd) +{ + struct adv7842_state *state = to_state(sd); + int err; + + err = cec_register_adapter(state->cec_adap); + if (err) + cec_delete_adapter(state->cec_adap); + return err; +} + +static void adv7842_unregistered(struct v4l2_subdev *sd) +{ + struct adv7842_state *state = to_state(sd); + + cec_unregister_adapter(state->cec_adap); +} + /* ----------------------------------------------------------------------- */ static const struct v4l2_ctrl_ops adv7842_ctrl_ops = { @@ -3077,6 +3306,11 @@ static const struct v4l2_subdev_ops adv7842_ops = { .pad = &adv7842_pad_ops, }; +static const struct v4l2_subdev_internal_ops adv7842_int_ops = { + .registered = adv7842_registered, + .unregistered = adv7842_unregistered, +}; + /* -------------------------- custom ctrls ---------------------------------- */ static const struct v4l2_ctrl_config adv7842_ctrl_analog_sampling_phase = { @@ -3241,6 +3475,7 @@ static int adv7842_probe(struct i2c_client *client, sd = &state->sd; v4l2_i2c_subdev_init(sd, client, &adv7842_ops); sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; + sd->internal_ops = &adv7842_int_ops; state->mode = pdata->mode; state->hdmi_port_a = pdata->input == ADV7842_SELECT_HDMI_PORT_A; @@ -3331,6 +3566,17 @@ static int adv7842_probe(struct i2c_client *client, if (err) goto err_entity; +#if IS_ENABLED(CONFIG_VIDEO_ADV7842_CEC) + state->cec_adap = cec_allocate_adapter(&adv7842_cec_adap_ops, + state, dev_name(&client->dev), + CEC_CAP_TRANSMIT | CEC_CAP_LOG_ADDRS | + CEC_CAP_PASSTHROUGH | CEC_CAP_RC, ADV7842_MAX_ADDRS, + &client->dev); + err = PTR_ERR_OR_ZERO(state->cec_adap); + if (err) + goto err_entity; +#endif + v4l2_info(sd, "%s found @ 0x%x (%s)\n", client->name, client->addr << 1, client->adapter->name); return 0; @@ -3355,7 +3601,6 @@ static int adv7842_remove(struct i2c_client *client) struct adv7842_state *state = to_state(sd); adv7842_irq_enable(sd, false); - cancel_delayed_work(&state->delayed_work_enable_hotplug); destroy_workqueue(state->work_queues); v4l2_device_unregister_subdev(sd); diff --git a/drivers/media/i2c/mt9t001.c b/drivers/media/i2c/mt9t001.c index 702d562f8e39..842017fa4aab 100644 --- a/drivers/media/i2c/mt9t001.c +++ b/drivers/media/i2c/mt9t001.c @@ -233,10 +233,21 @@ static int __mt9t001_set_power(struct mt9t001 *mt9t001, bool on) ret = mt9t001_reset(mt9t001); if (ret < 0) { dev_err(&client->dev, "Failed to reset the camera\n"); - return ret; + goto e_power; } - return v4l2_ctrl_handler_setup(&mt9t001->ctrls); + ret = v4l2_ctrl_handler_setup(&mt9t001->ctrls); + if (ret < 0) { + dev_err(&client->dev, "Failed to set up control handlers\n"); + goto e_power; + } + + return 0; + +e_power: + mt9t001_power_off(mt9t001); + + return ret; } /* ----------------------------------------------------------------------------- @@ -834,7 +845,7 @@ static struct v4l2_subdev_ops mt9t001_subdev_ops = { .pad = &mt9t001_subdev_pad_ops, }; -static struct v4l2_subdev_internal_ops mt9t001_subdev_internal_ops = { +static const struct v4l2_subdev_internal_ops mt9t001_subdev_internal_ops = { .registered = mt9t001_registered, .open = mt9t001_open, .close = mt9t001_close, diff --git a/drivers/media/i2c/mt9v032.c b/drivers/media/i2c/mt9v032.c index 501b37039449..58eb62f1ba21 100644 --- a/drivers/media/i2c/mt9v032.c +++ b/drivers/media/i2c/mt9v032.c @@ -19,7 +19,6 @@ #include <linux/log2.h> #include <linux/mutex.h> #include <linux/of.h> -#include <linux/of_gpio.h> #include <linux/regmap.h> #include <linux/slab.h> #include <linux/videodev2.h> @@ -133,9 +132,16 @@ #define MT9V032_TEST_PATTERN_GRAY_DIAGONAL (3 << 11) #define MT9V032_TEST_PATTERN_ENABLE (1 << 13) #define MT9V032_TEST_PATTERN_FLIP (1 << 14) +#define MT9V032_AEGC_DESIRED_BIN 0xa5 +#define MT9V032_AEC_UPDATE_FREQUENCY 0xa6 +#define MT9V032_AEC_LPF 0xa8 +#define MT9V032_AGC_UPDATE_FREQUENCY 0xa9 +#define MT9V032_AGC_LPF 0xaa #define MT9V032_AEC_AGC_ENABLE 0xaf #define MT9V032_AEC_ENABLE (1 << 0) #define MT9V032_AGC_ENABLE (1 << 1) +#define MT9V034_AEC_MAX_SHUTTER_WIDTH 0xad +#define MT9V032_AEC_MAX_SHUTTER_WIDTH 0xbd #define MT9V032_THERMAL_INFO 0xc1 enum mt9v032_model { @@ -162,6 +168,8 @@ struct mt9v032_model_data { unsigned int min_shutter; unsigned int max_shutter; unsigned int pclk_reg; + unsigned int aec_max_shutter_reg; + const struct v4l2_ctrl_config * const aec_max_shutter_v4l2_ctrl; }; struct mt9v032_model_info { @@ -175,63 +183,6 @@ static const struct mt9v032_model_version mt9v032_versions[] = { { MT9V034_CHIP_ID_REV1, "MT9V024/MT9V034 rev1" }, }; -static const struct mt9v032_model_data mt9v032_model_data[] = { - { - /* MT9V022, MT9V032 revisions 1/2/3 */ - .min_row_time = 660, - .min_hblank = MT9V032_HORIZONTAL_BLANKING_MIN, - .min_vblank = MT9V032_VERTICAL_BLANKING_MIN, - .max_vblank = MT9V032_VERTICAL_BLANKING_MAX, - .min_shutter = MT9V032_TOTAL_SHUTTER_WIDTH_MIN, - .max_shutter = MT9V032_TOTAL_SHUTTER_WIDTH_MAX, - .pclk_reg = MT9V032_PIXEL_CLOCK, - }, { - /* MT9V024, MT9V034 */ - .min_row_time = 690, - .min_hblank = MT9V034_HORIZONTAL_BLANKING_MIN, - .min_vblank = MT9V034_VERTICAL_BLANKING_MIN, - .max_vblank = MT9V034_VERTICAL_BLANKING_MAX, - .min_shutter = MT9V034_TOTAL_SHUTTER_WIDTH_MIN, - .max_shutter = MT9V034_TOTAL_SHUTTER_WIDTH_MAX, - .pclk_reg = MT9V034_PIXEL_CLOCK, - }, -}; - -static const struct mt9v032_model_info mt9v032_models[] = { - [MT9V032_MODEL_V022_COLOR] = { - .data = &mt9v032_model_data[0], - .color = true, - }, - [MT9V032_MODEL_V022_MONO] = { - .data = &mt9v032_model_data[0], - .color = false, - }, - [MT9V032_MODEL_V024_COLOR] = { - .data = &mt9v032_model_data[1], - .color = true, - }, - [MT9V032_MODEL_V024_MONO] = { - .data = &mt9v032_model_data[1], - .color = false, - }, - [MT9V032_MODEL_V032_COLOR] = { - .data = &mt9v032_model_data[0], - .color = true, - }, - [MT9V032_MODEL_V032_MONO] = { - .data = &mt9v032_model_data[0], - .color = false, - }, - [MT9V032_MODEL_V034_COLOR] = { - .data = &mt9v032_model_data[1], - .color = true, - }, - [MT9V032_MODEL_V034_MONO] = { - .data = &mt9v032_model_data[1], - .color = false, - }, -}; - struct mt9v032 { struct v4l2_subdev subdev; struct media_pad pad; @@ -349,7 +300,8 @@ static int mt9v032_power_on(struct mt9v032 *mt9v032) if (ret < 0) return ret; - return regmap_write(map, MT9V032_CHIP_CONTROL, 0); + return regmap_write(map, MT9V032_CHIP_CONTROL, + MT9V032_CHIP_CONTROL_MASTER_MODE); } static void mt9v032_power_off(struct mt9v032 *mt9v032) @@ -421,8 +373,7 @@ __mt9v032_get_pad_crop(struct mt9v032 *mt9v032, struct v4l2_subdev_pad_config *c static int mt9v032_s_stream(struct v4l2_subdev *subdev, int enable) { - const u16 mode = MT9V032_CHIP_CONTROL_MASTER_MODE - | MT9V032_CHIP_CONTROL_DOUT_ENABLE + const u16 mode = MT9V032_CHIP_CONTROL_DOUT_ENABLE | MT9V032_CHIP_CONTROL_SEQUENTIAL; struct mt9v032 *mt9v032 = to_mt9v032(subdev); struct v4l2_rect *crop = &mt9v032->crop; @@ -647,6 +598,34 @@ static int mt9v032_set_selection(struct v4l2_subdev *subdev, */ #define V4L2_CID_TEST_PATTERN_COLOR (V4L2_CID_USER_BASE | 0x1001) +/* + * Value between 1 and 64 to set the desired bin. This is effectively a measure + * of how bright the image is supposed to be. Both AGC and AEC try to reach + * this. + */ +#define V4L2_CID_AEGC_DESIRED_BIN (V4L2_CID_USER_BASE | 0x1002) +/* + * LPF is the low pass filter capability of the chip. Both AEC and AGC have + * this setting. This limits the speed in which AGC/AEC adjust their settings. + * Possible values are 0-2. 0 means no LPF. For 1 and 2 this equation is used: + * + * if |(calculated new exp - current exp)| > (current exp / 4) + * next exp = calculated new exp + * else + * next exp = current exp + ((calculated new exp - current exp) / 2^LPF) + */ +#define V4L2_CID_AEC_LPF (V4L2_CID_USER_BASE | 0x1003) +#define V4L2_CID_AGC_LPF (V4L2_CID_USER_BASE | 0x1004) +/* + * Value between 0 and 15. This is the number of frames being skipped before + * updating the auto exposure/gain. + */ +#define V4L2_CID_AEC_UPDATE_INTERVAL (V4L2_CID_USER_BASE | 0x1005) +#define V4L2_CID_AGC_UPDATE_INTERVAL (V4L2_CID_USER_BASE | 0x1006) +/* + * Maximum shutter width used for AEC. + */ +#define V4L2_CID_AEC_MAX_SHUTTER_WIDTH (V4L2_CID_USER_BASE | 0x1007) static int mt9v032_s_ctrl(struct v4l2_ctrl *ctrl) { @@ -716,6 +695,28 @@ static int mt9v032_s_ctrl(struct v4l2_ctrl *ctrl) break; } return regmap_write(map, MT9V032_TEST_PATTERN, data); + + case V4L2_CID_AEGC_DESIRED_BIN: + return regmap_write(map, MT9V032_AEGC_DESIRED_BIN, ctrl->val); + + case V4L2_CID_AEC_LPF: + return regmap_write(map, MT9V032_AEC_LPF, ctrl->val); + + case V4L2_CID_AGC_LPF: + return regmap_write(map, MT9V032_AGC_LPF, ctrl->val); + + case V4L2_CID_AEC_UPDATE_INTERVAL: + return regmap_write(map, MT9V032_AEC_UPDATE_FREQUENCY, + ctrl->val); + + case V4L2_CID_AGC_UPDATE_INTERVAL: + return regmap_write(map, MT9V032_AGC_UPDATE_FREQUENCY, + ctrl->val); + + case V4L2_CID_AEC_MAX_SHUTTER_WIDTH: + return regmap_write(map, + mt9v032->model->data->aec_max_shutter_reg, + ctrl->val); } return 0; @@ -745,6 +746,84 @@ static const struct v4l2_ctrl_config mt9v032_test_pattern_color = { .flags = 0, }; +static const struct v4l2_ctrl_config mt9v032_aegc_controls[] = { + { + .ops = &mt9v032_ctrl_ops, + .id = V4L2_CID_AEGC_DESIRED_BIN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "AEC/AGC Desired Bin", + .min = 1, + .max = 64, + .step = 1, + .def = 58, + .flags = 0, + }, { + .ops = &mt9v032_ctrl_ops, + .id = V4L2_CID_AEC_LPF, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "AEC Low Pass Filter", + .min = 0, + .max = 2, + .step = 1, + .def = 0, + .flags = 0, + }, { + .ops = &mt9v032_ctrl_ops, + .id = V4L2_CID_AGC_LPF, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "AGC Low Pass Filter", + .min = 0, + .max = 2, + .step = 1, + .def = 2, + .flags = 0, + }, { + .ops = &mt9v032_ctrl_ops, + .id = V4L2_CID_AEC_UPDATE_INTERVAL, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "AEC Update Interval", + .min = 0, + .max = 16, + .step = 1, + .def = 2, + .flags = 0, + }, { + .ops = &mt9v032_ctrl_ops, + .id = V4L2_CID_AGC_UPDATE_INTERVAL, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "AGC Update Interval", + .min = 0, + .max = 16, + .step = 1, + .def = 2, + .flags = 0, + } +}; + +static const struct v4l2_ctrl_config mt9v032_aec_max_shutter_width = { + .ops = &mt9v032_ctrl_ops, + .id = V4L2_CID_AEC_MAX_SHUTTER_WIDTH, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "AEC Max Shutter Width", + .min = 1, + .max = 2047, + .step = 1, + .def = 480, + .flags = 0, +}; + +static const struct v4l2_ctrl_config mt9v034_aec_max_shutter_width = { + .ops = &mt9v032_ctrl_ops, + .id = V4L2_CID_AEC_MAX_SHUTTER_WIDTH, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "AEC Max Shutter Width", + .min = 1, + .max = 32765, + .step = 1, + .def = 480, + .flags = 0, +}; + /* ----------------------------------------------------------------------------- * V4L2 subdev core operations */ @@ -953,13 +1032,6 @@ static int mt9v032_probe(struct i2c_client *client, unsigned int i; int ret; - if (!i2c_check_functionality(client->adapter, - I2C_FUNC_SMBUS_WORD_DATA)) { - dev_warn(&client->adapter->dev, - "I2C-Adapter doesn't support I2C_FUNC_SMBUS_WORD\n"); - return -EIO; - } - mt9v032 = devm_kzalloc(&client->dev, sizeof(*mt9v032), GFP_KERNEL); if (!mt9v032) return -ENOMEM; @@ -986,7 +1058,8 @@ static int mt9v032_probe(struct i2c_client *client, mt9v032->pdata = pdata; mt9v032->model = (const void *)did->driver_data; - v4l2_ctrl_handler_init(&mt9v032->ctrls, 10); + v4l2_ctrl_handler_init(&mt9v032->ctrls, 11 + + ARRAY_SIZE(mt9v032_aegc_controls)); v4l2_ctrl_new_std(&mt9v032->ctrls, &mt9v032_ctrl_ops, V4L2_CID_AUTOGAIN, 0, 1, 1, 1); @@ -1015,6 +1088,13 @@ static int mt9v032_probe(struct i2c_client *client, mt9v032->test_pattern_color = v4l2_ctrl_new_custom(&mt9v032->ctrls, &mt9v032_test_pattern_color, NULL); + v4l2_ctrl_new_custom(&mt9v032->ctrls, + mt9v032->model->data->aec_max_shutter_v4l2_ctrl, + NULL); + for (i = 0; i < ARRAY_SIZE(mt9v032_aegc_controls); ++i) + v4l2_ctrl_new_custom(&mt9v032->ctrls, &mt9v032_aegc_controls[i], + NULL); + v4l2_ctrl_cluster(2, &mt9v032->test_pattern); mt9v032->pixel_rate = @@ -1103,6 +1183,67 @@ static int mt9v032_remove(struct i2c_client *client) return 0; } +static const struct mt9v032_model_data mt9v032_model_data[] = { + { + /* MT9V022, MT9V032 revisions 1/2/3 */ + .min_row_time = 660, + .min_hblank = MT9V032_HORIZONTAL_BLANKING_MIN, + .min_vblank = MT9V032_VERTICAL_BLANKING_MIN, + .max_vblank = MT9V032_VERTICAL_BLANKING_MAX, + .min_shutter = MT9V032_TOTAL_SHUTTER_WIDTH_MIN, + .max_shutter = MT9V032_TOTAL_SHUTTER_WIDTH_MAX, + .pclk_reg = MT9V032_PIXEL_CLOCK, + .aec_max_shutter_reg = MT9V032_AEC_MAX_SHUTTER_WIDTH, + .aec_max_shutter_v4l2_ctrl = &mt9v032_aec_max_shutter_width, + }, { + /* MT9V024, MT9V034 */ + .min_row_time = 690, + .min_hblank = MT9V034_HORIZONTAL_BLANKING_MIN, + .min_vblank = MT9V034_VERTICAL_BLANKING_MIN, + .max_vblank = MT9V034_VERTICAL_BLANKING_MAX, + .min_shutter = MT9V034_TOTAL_SHUTTER_WIDTH_MIN, + .max_shutter = MT9V034_TOTAL_SHUTTER_WIDTH_MAX, + .pclk_reg = MT9V034_PIXEL_CLOCK, + .aec_max_shutter_reg = MT9V034_AEC_MAX_SHUTTER_WIDTH, + .aec_max_shutter_v4l2_ctrl = &mt9v034_aec_max_shutter_width, + }, +}; + +static const struct mt9v032_model_info mt9v032_models[] = { + [MT9V032_MODEL_V022_COLOR] = { + .data = &mt9v032_model_data[0], + .color = true, + }, + [MT9V032_MODEL_V022_MONO] = { + .data = &mt9v032_model_data[0], + .color = false, + }, + [MT9V032_MODEL_V024_COLOR] = { + .data = &mt9v032_model_data[1], + .color = true, + }, + [MT9V032_MODEL_V024_MONO] = { + .data = &mt9v032_model_data[1], + .color = false, + }, + [MT9V032_MODEL_V032_COLOR] = { + .data = &mt9v032_model_data[0], + .color = true, + }, + [MT9V032_MODEL_V032_MONO] = { + .data = &mt9v032_model_data[0], + .color = false, + }, + [MT9V032_MODEL_V034_COLOR] = { + .data = &mt9v032_model_data[1], + .color = true, + }, + [MT9V032_MODEL_V034_MONO] = { + .data = &mt9v032_model_data[1], + .color = false, + }, +}; + static const struct i2c_device_id mt9v032_id[] = { { "mt9v022", (kernel_ulong_t)&mt9v032_models[MT9V032_MODEL_V022_COLOR] }, { "mt9v022m", (kernel_ulong_t)&mt9v032_models[MT9V032_MODEL_V022_MONO] }, diff --git a/drivers/media/i2c/smiapp/smiapp-core.c b/drivers/media/i2c/smiapp/smiapp-core.c index 3dfe387abf6e..d08ab6c8357c 100644 --- a/drivers/media/i2c/smiapp/smiapp-core.c +++ b/drivers/media/i2c/smiapp/smiapp-core.c @@ -3044,10 +3044,8 @@ static struct smiapp_platform_data *smiapp_get_pdata(struct device *dev) pdata->op_sys_clock = devm_kcalloc( dev, bus_cfg->nr_of_link_frequencies + 1 /* guardian */, sizeof(*pdata->op_sys_clock), GFP_KERNEL); - if (!pdata->op_sys_clock) { - rval = -ENOMEM; + if (!pdata->op_sys_clock) goto out_err; - } for (i = 0; i < bus_cfg->nr_of_link_frequencies; i++) { pdata->op_sys_clock[i] = bus_cfg->link_frequencies[i]; diff --git a/drivers/media/media-device.c b/drivers/media/media-device.c index a1cd50f331f1..1795abeda658 100644 --- a/drivers/media/media-device.c +++ b/drivers/media/media-device.c @@ -423,7 +423,7 @@ static long media_device_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct media_devnode *devnode = media_devnode_data(filp); - struct media_device *dev = to_media_device(devnode); + struct media_device *dev = devnode->media_dev; long ret; mutex_lock(&dev->graph_mutex); @@ -495,7 +495,7 @@ static long media_device_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct media_devnode *devnode = media_devnode_data(filp); - struct media_device *dev = to_media_device(devnode); + struct media_device *dev = devnode->media_dev; long ret; switch (cmd) { @@ -531,7 +531,8 @@ static const struct media_file_operations media_device_fops = { static ssize_t show_model(struct device *cd, struct device_attribute *attr, char *buf) { - struct media_device *mdev = to_media_device(to_media_devnode(cd)); + struct media_devnode *devnode = to_media_devnode(cd); + struct media_device *mdev = devnode->media_dev; return sprintf(buf, "%.*s\n", (int)sizeof(mdev->model), mdev->model); } @@ -704,23 +705,35 @@ EXPORT_SYMBOL_GPL(media_device_cleanup); int __must_check __media_device_register(struct media_device *mdev, struct module *owner) { + struct media_devnode *devnode; int ret; + devnode = kzalloc(sizeof(*devnode), GFP_KERNEL); + if (!devnode) + return -ENOMEM; + /* Register the device node. */ - mdev->devnode.fops = &media_device_fops; - mdev->devnode.parent = mdev->dev; - mdev->devnode.release = media_device_release; + mdev->devnode = devnode; + devnode->fops = &media_device_fops; + devnode->parent = mdev->dev; + devnode->release = media_device_release; /* Set version 0 to indicate user-space that the graph is static */ mdev->topology_version = 0; - ret = media_devnode_register(&mdev->devnode, owner); - if (ret < 0) + ret = media_devnode_register(mdev, devnode, owner); + if (ret < 0) { + /* devnode free is handled in media_devnode_*() */ + mdev->devnode = NULL; return ret; + } - ret = device_create_file(&mdev->devnode.dev, &dev_attr_model); + ret = device_create_file(&devnode->dev, &dev_attr_model); if (ret < 0) { - media_devnode_unregister(&mdev->devnode); + /* devnode free is handled in media_devnode_*() */ + mdev->devnode = NULL; + media_devnode_unregister_prepare(devnode); + media_devnode_unregister(devnode); return ret; } @@ -771,11 +784,14 @@ void media_device_unregister(struct media_device *mdev) mutex_lock(&mdev->graph_mutex); /* Check if mdev was ever registered at all */ - if (!media_devnode_is_registered(&mdev->devnode)) { + if (!media_devnode_is_registered(mdev->devnode)) { mutex_unlock(&mdev->graph_mutex); return; } + /* Clear the devnode register bit to avoid races with media dev open */ + media_devnode_unregister_prepare(mdev->devnode); + /* Remove all entities from the media device */ list_for_each_entry_safe(entity, next, &mdev->entities, graph_obj.list) __media_device_unregister_entity(entity); @@ -794,9 +810,12 @@ void media_device_unregister(struct media_device *mdev) mutex_unlock(&mdev->graph_mutex); - device_remove_file(&mdev->devnode.dev, &dev_attr_model); - dev_dbg(mdev->dev, "Media device unregistering\n"); - media_devnode_unregister(&mdev->devnode); + dev_dbg(mdev->dev, "Media device unregistered\n"); + + device_remove_file(&mdev->devnode->dev, &dev_attr_model); + media_devnode_unregister(mdev->devnode); + /* devnode free is handled in media_devnode_*() */ + mdev->devnode = NULL; } EXPORT_SYMBOL_GPL(media_device_unregister); diff --git a/drivers/media/media-devnode.c b/drivers/media/media-devnode.c index b66dc9d0766b..f2772ba6f611 100644 --- a/drivers/media/media-devnode.c +++ b/drivers/media/media-devnode.c @@ -44,6 +44,7 @@ #include <linux/uaccess.h> #include <media/media-devnode.h> +#include <media/media-device.h> #define MEDIA_NUM_DEVICES 256 #define MEDIA_NAME "media" @@ -59,21 +60,19 @@ static DECLARE_BITMAP(media_devnode_nums, MEDIA_NUM_DEVICES); /* Called when the last user of the media device exits. */ static void media_devnode_release(struct device *cd) { - struct media_devnode *mdev = to_media_devnode(cd); + struct media_devnode *devnode = to_media_devnode(cd); mutex_lock(&media_devnode_lock); - - /* Delete the cdev on this minor as well */ - cdev_del(&mdev->cdev); - /* Mark device node number as free */ - clear_bit(mdev->minor, media_devnode_nums); - + clear_bit(devnode->minor, media_devnode_nums); mutex_unlock(&media_devnode_lock); /* Release media_devnode and perform other cleanups as needed. */ - if (mdev->release) - mdev->release(mdev); + if (devnode->release) + devnode->release(devnode); + + kfree(devnode); + pr_debug("%s: Media Devnode Deallocated\n", __func__); } static struct bus_type media_bus_type = { @@ -83,37 +82,37 @@ static struct bus_type media_bus_type = { static ssize_t media_read(struct file *filp, char __user *buf, size_t sz, loff_t *off) { - struct media_devnode *mdev = media_devnode_data(filp); + struct media_devnode *devnode = media_devnode_data(filp); - if (!mdev->fops->read) + if (!devnode->fops->read) return -EINVAL; - if (!media_devnode_is_registered(mdev)) + if (!media_devnode_is_registered(devnode)) return -EIO; - return mdev->fops->read(filp, buf, sz, off); + return devnode->fops->read(filp, buf, sz, off); } static ssize_t media_write(struct file *filp, const char __user *buf, size_t sz, loff_t *off) { - struct media_devnode *mdev = media_devnode_data(filp); + struct media_devnode *devnode = media_devnode_data(filp); - if (!mdev->fops->write) + if (!devnode->fops->write) return -EINVAL; - if (!media_devnode_is_registered(mdev)) + if (!media_devnode_is_registered(devnode)) return -EIO; - return mdev->fops->write(filp, buf, sz, off); + return devnode->fops->write(filp, buf, sz, off); } static unsigned int media_poll(struct file *filp, struct poll_table_struct *poll) { - struct media_devnode *mdev = media_devnode_data(filp); + struct media_devnode *devnode = media_devnode_data(filp); - if (!media_devnode_is_registered(mdev)) + if (!media_devnode_is_registered(devnode)) return POLLERR | POLLHUP; - if (!mdev->fops->poll) + if (!devnode->fops->poll) return DEFAULT_POLLMASK; - return mdev->fops->poll(filp, poll); + return devnode->fops->poll(filp, poll); } static long @@ -121,12 +120,12 @@ __media_ioctl(struct file *filp, unsigned int cmd, unsigned long arg, long (*ioctl_func)(struct file *filp, unsigned int cmd, unsigned long arg)) { - struct media_devnode *mdev = media_devnode_data(filp); + struct media_devnode *devnode = media_devnode_data(filp); if (!ioctl_func) return -ENOTTY; - if (!media_devnode_is_registered(mdev)) + if (!media_devnode_is_registered(devnode)) return -EIO; return ioctl_func(filp, cmd, arg); @@ -134,9 +133,9 @@ __media_ioctl(struct file *filp, unsigned int cmd, unsigned long arg, static long media_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { - struct media_devnode *mdev = media_devnode_data(filp); + struct media_devnode *devnode = media_devnode_data(filp); - return __media_ioctl(filp, cmd, arg, mdev->fops->ioctl); + return __media_ioctl(filp, cmd, arg, devnode->fops->ioctl); } #ifdef CONFIG_COMPAT @@ -144,9 +143,9 @@ static long media_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) static long media_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { - struct media_devnode *mdev = media_devnode_data(filp); + struct media_devnode *devnode = media_devnode_data(filp); - return __media_ioctl(filp, cmd, arg, mdev->fops->compat_ioctl); + return __media_ioctl(filp, cmd, arg, devnode->fops->compat_ioctl); } #endif /* CONFIG_COMPAT */ @@ -154,7 +153,7 @@ static long media_compat_ioctl(struct file *filp, unsigned int cmd, /* Override for the open function */ static int media_open(struct inode *inode, struct file *filp) { - struct media_devnode *mdev; + struct media_devnode *devnode; int ret; /* Check if the media device is available. This needs to be done with @@ -164,23 +163,23 @@ static int media_open(struct inode *inode, struct file *filp) * a crash. */ mutex_lock(&media_devnode_lock); - mdev = container_of(inode->i_cdev, struct media_devnode, cdev); + devnode = container_of(inode->i_cdev, struct media_devnode, cdev); /* return ENXIO if the media device has been removed already or if it is not registered anymore. */ - if (!media_devnode_is_registered(mdev)) { + if (!media_devnode_is_registered(devnode)) { mutex_unlock(&media_devnode_lock); return -ENXIO; } /* and increase the device refcount */ - get_device(&mdev->dev); + get_device(&devnode->dev); mutex_unlock(&media_devnode_lock); - filp->private_data = mdev; + filp->private_data = devnode; - if (mdev->fops->open) { - ret = mdev->fops->open(filp); + if (devnode->fops->open) { + ret = devnode->fops->open(filp); if (ret) { - put_device(&mdev->dev); + put_device(&devnode->dev); filp->private_data = NULL; return ret; } @@ -192,16 +191,18 @@ static int media_open(struct inode *inode, struct file *filp) /* Override for the release function */ static int media_release(struct inode *inode, struct file *filp) { - struct media_devnode *mdev = media_devnode_data(filp); + struct media_devnode *devnode = media_devnode_data(filp); - if (mdev->fops->release) - mdev->fops->release(filp); + if (devnode->fops->release) + devnode->fops->release(filp); filp->private_data = NULL; /* decrease the refcount unconditionally since the release() return value is ignored. */ - put_device(&mdev->dev); + put_device(&devnode->dev); + + pr_debug("%s: Media Release\n", __func__); return 0; } @@ -219,7 +220,8 @@ static const struct file_operations media_devnode_fops = { .llseek = no_llseek, }; -int __must_check media_devnode_register(struct media_devnode *mdev, +int __must_check media_devnode_register(struct media_device *mdev, + struct media_devnode *devnode, struct module *owner) { int minor; @@ -231,61 +233,80 @@ int __must_check media_devnode_register(struct media_devnode *mdev, if (minor == MEDIA_NUM_DEVICES) { mutex_unlock(&media_devnode_lock); pr_err("could not get a free minor\n"); + kfree(devnode); return -ENFILE; } set_bit(minor, media_devnode_nums); mutex_unlock(&media_devnode_lock); - mdev->minor = minor; + devnode->minor = minor; + devnode->media_dev = mdev; + + /* Part 1: Initialize dev now to use dev.kobj for cdev.kobj.parent */ + devnode->dev.bus = &media_bus_type; + devnode->dev.devt = MKDEV(MAJOR(media_dev_t), devnode->minor); + devnode->dev.release = media_devnode_release; + if (devnode->parent) + devnode->dev.parent = devnode->parent; + dev_set_name(&devnode->dev, "media%d", devnode->minor); + device_initialize(&devnode->dev); /* Part 2: Initialize and register the character device */ - cdev_init(&mdev->cdev, &media_devnode_fops); - mdev->cdev.owner = owner; + cdev_init(&devnode->cdev, &media_devnode_fops); + devnode->cdev.owner = owner; + devnode->cdev.kobj.parent = &devnode->dev.kobj; - ret = cdev_add(&mdev->cdev, MKDEV(MAJOR(media_dev_t), mdev->minor), 1); + ret = cdev_add(&devnode->cdev, MKDEV(MAJOR(media_dev_t), devnode->minor), 1); if (ret < 0) { pr_err("%s: cdev_add failed\n", __func__); - goto error; + goto cdev_add_error; } - /* Part 3: Register the media device */ - mdev->dev.bus = &media_bus_type; - mdev->dev.devt = MKDEV(MAJOR(media_dev_t), mdev->minor); - mdev->dev.release = media_devnode_release; - if (mdev->parent) - mdev->dev.parent = mdev->parent; - dev_set_name(&mdev->dev, "media%d", mdev->minor); - ret = device_register(&mdev->dev); + /* Part 3: Add the media device */ + ret = device_add(&devnode->dev); if (ret < 0) { - pr_err("%s: device_register failed\n", __func__); - goto error; + pr_err("%s: device_add failed\n", __func__); + goto device_add_error; } /* Part 4: Activate this minor. The char device can now be used. */ - set_bit(MEDIA_FLAG_REGISTERED, &mdev->flags); + set_bit(MEDIA_FLAG_REGISTERED, &devnode->flags); return 0; -error: +device_add_error: + cdev_del(&devnode->cdev); +cdev_add_error: mutex_lock(&media_devnode_lock); - cdev_del(&mdev->cdev); - clear_bit(mdev->minor, media_devnode_nums); + clear_bit(devnode->minor, media_devnode_nums); + devnode->media_dev = NULL; mutex_unlock(&media_devnode_lock); + put_device(&devnode->dev); return ret; } -void media_devnode_unregister(struct media_devnode *mdev) +void media_devnode_unregister_prepare(struct media_devnode *devnode) { - /* Check if mdev was ever registered at all */ - if (!media_devnode_is_registered(mdev)) + /* Check if devnode was ever registered at all */ + if (!media_devnode_is_registered(devnode)) return; mutex_lock(&media_devnode_lock); - clear_bit(MEDIA_FLAG_REGISTERED, &mdev->flags); + clear_bit(MEDIA_FLAG_REGISTERED, &devnode->flags); + mutex_unlock(&media_devnode_lock); +} + +void media_devnode_unregister(struct media_devnode *devnode) +{ + mutex_lock(&media_devnode_lock); + /* Delete the cdev on this minor as well */ + cdev_del(&devnode->cdev); mutex_unlock(&media_devnode_lock); - device_unregister(&mdev->dev); + device_del(&devnode->dev); + devnode->media_dev = NULL; + put_device(&devnode->dev); } /* diff --git a/drivers/media/pci/cx18/cx18-driver.c b/drivers/media/pci/cx18/cx18-driver.c index 260e462d91b4..2f23b26b16c0 100644 --- a/drivers/media/pci/cx18/cx18-driver.c +++ b/drivers/media/pci/cx18/cx18-driver.c @@ -560,7 +560,7 @@ static void cx18_process_options(struct cx18 *cx) cx->stream_buf_size[CX18_ENC_STREAM_TYPE_MPG] = enc_mpg_bufsize; cx->stream_buf_size[CX18_ENC_STREAM_TYPE_IDX] = enc_idx_bufsize; cx->stream_buf_size[CX18_ENC_STREAM_TYPE_YUV] = enc_yuv_bufsize; - cx->stream_buf_size[CX18_ENC_STREAM_TYPE_VBI] = vbi_active_samples * 36; + cx->stream_buf_size[CX18_ENC_STREAM_TYPE_VBI] = VBI_ACTIVE_SAMPLES * 36; cx->stream_buf_size[CX18_ENC_STREAM_TYPE_PCM] = enc_pcm_bufsize; cx->stream_buf_size[CX18_ENC_STREAM_TYPE_RAD] = 0; /* control no data */ diff --git a/drivers/media/pci/cx18/cx18-driver.h b/drivers/media/pci/cx18/cx18-driver.h index 47ce80fa73b9..ef308a10e870 100644 --- a/drivers/media/pci/cx18/cx18-driver.h +++ b/drivers/media/pci/cx18/cx18-driver.h @@ -492,9 +492,9 @@ struct cx18_card; * (1/15.625 kHz) * 2 * 13.5 MHz = 1728 samples/line = * 4 bytes SAV + 280 bytes anc data + 4 bytes SAV + 1440 active samples */ -static const u32 vbi_active_samples = 1444; /* 4 byte SAV + 720 Y + 720 U/V */ -static const u32 vbi_hblank_samples_60Hz = 272; /* 4 byte EAV + 268 anc/fill */ -static const u32 vbi_hblank_samples_50Hz = 284; /* 4 byte EAV + 280 anc/fill */ +#define VBI_ACTIVE_SAMPLES 1444 /* 4 byte SAV + 720 Y + 720 U/V */ +#define VBI_HBLANK_SAMPLES_60HZ 272 /* 4 byte EAV + 268 anc/fill */ +#define VBI_HBLANK_SAMPLES_50HZ 284 /* 4 byte EAV + 280 anc/fill */ #define CX18_VBI_FRAMES 32 diff --git a/drivers/media/pci/cx18/cx18-ioctl.c b/drivers/media/pci/cx18/cx18-ioctl.c index eeb741c7db1b..fecca2a63891 100644 --- a/drivers/media/pci/cx18/cx18-ioctl.c +++ b/drivers/media/pci/cx18/cx18-ioctl.c @@ -177,7 +177,7 @@ static int cx18_g_fmt_vbi_cap(struct file *file, void *fh, vbifmt->sampling_rate = 27000000; vbifmt->offset = 248; /* FIXME - slightly wrong for both 50 & 60 Hz */ - vbifmt->samples_per_line = vbi_active_samples - 4; + vbifmt->samples_per_line = VBI_ACTIVE_SAMPLES - 4; vbifmt->sample_format = V4L2_PIX_FMT_GREY; vbifmt->start[0] = cx->vbi.start[0]; vbifmt->start[1] = cx->vbi.start[1]; diff --git a/drivers/media/pci/cx18/cx18-streams.c b/drivers/media/pci/cx18/cx18-streams.c index c9860845264f..f3802ec1b383 100644 --- a/drivers/media/pci/cx18/cx18-streams.c +++ b/drivers/media/pci/cx18/cx18-streams.c @@ -605,9 +605,9 @@ static void cx18_vbi_setup(struct cx18_stream *s) /* Lines per field */ data[1] = (lines / 2) | ((lines / 2) << 16); /* bytes per line */ - data[2] = (raw ? vbi_active_samples - : (cx->is_60hz ? vbi_hblank_samples_60Hz - : vbi_hblank_samples_50Hz)); + data[2] = (raw ? VBI_ACTIVE_SAMPLES + : (cx->is_60hz ? VBI_HBLANK_SAMPLES_60HZ + : VBI_HBLANK_SAMPLES_50HZ)); /* Every X number of frames a VBI interrupt arrives (frames as in 25 or 30 fps) */ data[3] = 1; @@ -761,7 +761,7 @@ static void cx18_stream_configure_mdls(struct cx18_stream *s) s->bufs_per_mdl = 1; if (cx18_raw_vbi(s->cx)) { s->mdl_size = (s->cx->is_60hz ? 12 : 18) - * 2 * vbi_active_samples; + * 2 * VBI_ACTIVE_SAMPLES; } else { /* * See comment in cx18_vbi_setup() below about the @@ -769,8 +769,8 @@ static void cx18_stream_configure_mdls(struct cx18_stream *s) * the lines on which EAV RP codes toggle. */ s->mdl_size = s->cx->is_60hz - ? (21 - 4 + 1) * 2 * vbi_hblank_samples_60Hz - : (23 - 2 + 1) * 2 * vbi_hblank_samples_50Hz; + ? (21 - 4 + 1) * 2 * VBI_HBLANK_SAMPLES_60HZ + : (23 - 2 + 1) * 2 * VBI_HBLANK_SAMPLES_50HZ; } break; default: diff --git a/drivers/media/pci/cx18/cx18-vbi.c b/drivers/media/pci/cx18/cx18-vbi.c index add99642f1e2..43360cbcf24b 100644 --- a/drivers/media/pci/cx18/cx18-vbi.c +++ b/drivers/media/pci/cx18/cx18-vbi.c @@ -108,7 +108,7 @@ static void copy_vbi_data(struct cx18 *cx, int lines, u32 pts_stamp) /* FIXME - this function ignores the input size. */ static u32 compress_raw_buf(struct cx18 *cx, u8 *buf, u32 size, u32 hdr_size) { - u32 line_size = vbi_active_samples; + u32 line_size = VBI_ACTIVE_SAMPLES; u32 lines = cx->vbi.count * 2; u8 *q = buf; u8 *p; @@ -145,8 +145,8 @@ static u32 compress_sliced_buf(struct cx18 *cx, u8 *buf, u32 size, struct v4l2_decode_vbi_line vbi; int i; u32 line = 0; - u32 line_size = cx->is_60hz ? vbi_hblank_samples_60Hz - : vbi_hblank_samples_50Hz; + u32 line_size = cx->is_60hz ? VBI_HBLANK_SAMPLES_60HZ + : VBI_HBLANK_SAMPLES_50HZ; /* find the first valid line */ for (i = hdr_size, buf += hdr_size; i < size; i++, buf++) { diff --git a/drivers/media/pci/cx25821/cx25821-alsa.c b/drivers/media/pci/cx25821/cx25821-alsa.c index b602eba2b601..df189b16af12 100644 --- a/drivers/media/pci/cx25821/cx25821-alsa.c +++ b/drivers/media/pci/cx25821/cx25821-alsa.c @@ -693,7 +693,7 @@ static int snd_cx25821_pcm(struct cx25821_audio_dev *chip, int device, * Only boards with eeprom and byte 1 at eeprom=1 have it */ -static const struct pci_device_id cx25821_audio_pci_tbl[] = { +static const struct pci_device_id __maybe_unused cx25821_audio_pci_tbl[] = { {0x14f1, 0x0920, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, {0,} }; diff --git a/drivers/media/pci/netup_unidvb/Kconfig b/drivers/media/pci/netup_unidvb/Kconfig index f277b0b10c2d..0ad37714c7fd 100644 --- a/drivers/media/pci/netup_unidvb/Kconfig +++ b/drivers/media/pci/netup_unidvb/Kconfig @@ -5,8 +5,13 @@ config DVB_NETUP_UNIDVB select VIDEOBUF2_VMALLOC select DVB_HORUS3A if MEDIA_SUBDRV_AUTOSELECT select DVB_ASCOT2E if MEDIA_SUBDRV_AUTOSELECT + select DVB_HELENE if MEDIA_SUBDRV_AUTOSELECT select DVB_LNBH25 if MEDIA_SUBDRV_AUTOSELECT select DVB_CXD2841ER if MEDIA_SUBDRV_AUTOSELECT ---help--- Support for NetUP PCI express Universal DVB card. - + help + Say Y when you want to support NetUP Dual Universal DVB card + Card can receive two independent streams in following standards: + DVB-S/S2, T/T2, C/C2 + Two CI slots available for CAM modules. diff --git a/drivers/media/pci/netup_unidvb/netup_unidvb.h b/drivers/media/pci/netup_unidvb/netup_unidvb.h index a67b28111905..39b08ecda1fc 100644 --- a/drivers/media/pci/netup_unidvb/netup_unidvb.h +++ b/drivers/media/pci/netup_unidvb/netup_unidvb.h @@ -50,6 +50,15 @@ #define NETUP_UNIDVB_IRQ_CAM0 (1 << 11) #define NETUP_UNIDVB_IRQ_CAM1 (1 << 12) +/* NetUP Universal DVB card hardware revisions and it's PCI device id's: + * 1.3 - CXD2841ER demod, ASCOT2E and HORUS3A tuners + * 1.4 - CXD2854ER demod, HELENE tuner +*/ +enum netup_hw_rev { + NETUP_HW_REV_1_3 = 0x18F6, + NETUP_HW_REV_1_4 = 0x18F7 +}; + struct netup_dma { u8 num; spinlock_t lock; @@ -119,6 +128,7 @@ struct netup_unidvb_dev { struct netup_dma dma[2]; struct netup_ci_state ci[2]; struct netup_spi *spi; + enum netup_hw_rev rev; }; int netup_i2c_register(struct netup_unidvb_dev *ndev); diff --git a/drivers/media/pci/netup_unidvb/netup_unidvb_ci.c b/drivers/media/pci/netup_unidvb/netup_unidvb_ci.c index f46ffac66ee9..f535270c2116 100644 --- a/drivers/media/pci/netup_unidvb/netup_unidvb_ci.c +++ b/drivers/media/pci/netup_unidvb/netup_unidvb_ci.c @@ -147,7 +147,7 @@ static int netup_unidvb_ci_read_attribute_mem(struct dvb_ca_en50221 *en50221, { struct netup_ci_state *state = en50221->data; struct netup_unidvb_dev *dev = state->dev; - u8 val = *((u8 __force *)state->membase8_io + addr); + u8 val = *((u8 __force *)state->membase8_config + addr); dev_dbg(&dev->pci_dev->dev, "%s(): addr=0x%x val=0x%x\n", __func__, addr, val); @@ -162,7 +162,7 @@ static int netup_unidvb_ci_write_attribute_mem(struct dvb_ca_en50221 *en50221, dev_dbg(&dev->pci_dev->dev, "%s(): addr=0x%x data=0x%x\n", __func__, addr, data); - *((u8 __force *)state->membase8_io + addr) = data; + *((u8 __force *)state->membase8_config + addr) = data; return 0; } diff --git a/drivers/media/pci/netup_unidvb/netup_unidvb_core.c b/drivers/media/pci/netup_unidvb/netup_unidvb_core.c index 2b667b315913..d278d4e151a0 100644 --- a/drivers/media/pci/netup_unidvb/netup_unidvb_core.c +++ b/drivers/media/pci/netup_unidvb/netup_unidvb_core.c @@ -34,6 +34,7 @@ #include "cxd2841er.h" #include "horus3a.h" #include "ascot2e.h" +#include "helene.h" #include "lnbh25.h" static int spi_enable; @@ -120,7 +121,8 @@ static int netup_unidvb_tuner_ctrl(void *priv, int is_dvb_tc); static void netup_unidvb_queue_cleanup(struct netup_dma *dma); static struct cxd2841er_config demod_config = { - .i2c_addr = 0xc8 + .i2c_addr = 0xc8, + .xtal = SONY_XTAL_24000 }; static struct horus3a_config horus3a_conf = { @@ -134,6 +136,12 @@ static struct ascot2e_config ascot2e_conf = { .set_tuner_callback = netup_unidvb_tuner_ctrl }; +static struct helene_config helene_conf = { + .i2c_address = 0xc0, + .xtal = SONY_HELENE_XTAL_24000, + .set_tuner_callback = netup_unidvb_tuner_ctrl +}; + static struct lnbh25_config lnbh25_conf = { .i2c_address = 0x10, .data2_config = LNBH25_TEN | LNBH25_EXTM @@ -152,6 +160,11 @@ static int netup_unidvb_tuner_ctrl(void *priv, int is_dvb_tc) __func__, dma->num, is_dvb_tc); reg = readb(ndev->bmmio0 + GPIO_REG_IO); mask = (dma->num == 0) ? GPIO_RFA_CTL : GPIO_RFB_CTL; + + /* inverted tuner control in hw rev. 1.4 */ + if (ndev->rev == NETUP_HW_REV_1_4) + is_dvb_tc = !is_dvb_tc; + if (!is_dvb_tc) reg |= mask; else @@ -372,7 +385,15 @@ static int netup_unidvb_queue_init(struct netup_dma *dma, static int netup_unidvb_dvb_init(struct netup_unidvb_dev *ndev, int num) { - struct vb2_dvb_frontend *fe0, *fe1, *fe2; + int fe_count = 2; + int i = 0; + struct vb2_dvb_frontend *fes[2]; + u8 fe_name[32]; + + if (ndev->rev == NETUP_HW_REV_1_3) + demod_config.xtal = SONY_XTAL_20500; + else + demod_config.xtal = SONY_XTAL_24000; if (num < 0 || num > 1) { dev_dbg(&ndev->pci_dev->dev, @@ -381,84 +402,96 @@ static int netup_unidvb_dvb_init(struct netup_unidvb_dev *ndev, } mutex_init(&ndev->frontends[num].lock); INIT_LIST_HEAD(&ndev->frontends[num].felist); - if (vb2_dvb_alloc_frontend(&ndev->frontends[num], 1) == NULL || - vb2_dvb_alloc_frontend( - &ndev->frontends[num], 2) == NULL || - vb2_dvb_alloc_frontend( - &ndev->frontends[num], 3) == NULL) { - dev_dbg(&ndev->pci_dev->dev, - "%s(): unable to allocate vb2_dvb_frontend\n", - __func__); - return -ENOMEM; + + for (i = 0; i < fe_count; i++) { + if (vb2_dvb_alloc_frontend(&ndev->frontends[num], i+1) + == NULL) { + dev_err(&ndev->pci_dev->dev, + "%s(): unable to allocate vb2_dvb_frontend\n", + __func__); + return -ENOMEM; + } } - fe0 = vb2_dvb_get_frontend(&ndev->frontends[num], 1); - fe1 = vb2_dvb_get_frontend(&ndev->frontends[num], 2); - fe2 = vb2_dvb_get_frontend(&ndev->frontends[num], 3); - if (fe0 == NULL || fe1 == NULL || fe2 == NULL) { - dev_dbg(&ndev->pci_dev->dev, - "%s(): frontends has not been allocated\n", __func__); - return -EINVAL; + + for (i = 0; i < fe_count; i++) { + fes[i] = vb2_dvb_get_frontend(&ndev->frontends[num], i+1); + if (fes[i] == NULL) { + dev_err(&ndev->pci_dev->dev, + "%s(): frontends has not been allocated\n", + __func__); + return -EINVAL; + } + } + + for (i = 0; i < fe_count; i++) { + netup_unidvb_queue_init(&ndev->dma[num], &fes[i]->dvb.dvbq); + snprintf(fe_name, sizeof(fe_name), "netup_fe%d", i); + fes[i]->dvb.name = fe_name; } - netup_unidvb_queue_init(&ndev->dma[num], &fe0->dvb.dvbq); - netup_unidvb_queue_init(&ndev->dma[num], &fe1->dvb.dvbq); - netup_unidvb_queue_init(&ndev->dma[num], &fe2->dvb.dvbq); - fe0->dvb.name = "netup_fe0"; - fe1->dvb.name = "netup_fe1"; - fe2->dvb.name = "netup_fe2"; - fe0->dvb.frontend = dvb_attach(cxd2841er_attach_s, + + fes[0]->dvb.frontend = dvb_attach(cxd2841er_attach_s, &demod_config, &ndev->i2c[num].adap); - if (fe0->dvb.frontend == NULL) { + if (fes[0]->dvb.frontend == NULL) { dev_dbg(&ndev->pci_dev->dev, "%s(): unable to attach DVB-S/S2 frontend\n", __func__); goto frontend_detach; } - horus3a_conf.set_tuner_priv = &ndev->dma[num]; - if (!dvb_attach(horus3a_attach, fe0->dvb.frontend, - &horus3a_conf, &ndev->i2c[num].adap)) { - dev_dbg(&ndev->pci_dev->dev, - "%s(): unable to attach DVB-S/S2 tuner frontend\n", - __func__); - goto frontend_detach; + + if (ndev->rev == NETUP_HW_REV_1_3) { + horus3a_conf.set_tuner_priv = &ndev->dma[num]; + if (!dvb_attach(horus3a_attach, fes[0]->dvb.frontend, + &horus3a_conf, &ndev->i2c[num].adap)) { + dev_dbg(&ndev->pci_dev->dev, + "%s(): unable to attach HORUS3A DVB-S/S2 tuner frontend\n", + __func__); + goto frontend_detach; + } + } else { + helene_conf.set_tuner_priv = &ndev->dma[num]; + if (!dvb_attach(helene_attach_s, fes[0]->dvb.frontend, + &helene_conf, &ndev->i2c[num].adap)) { + dev_err(&ndev->pci_dev->dev, + "%s(): unable to attach HELENE DVB-S/S2 tuner frontend\n", + __func__); + goto frontend_detach; + } } - if (!dvb_attach(lnbh25_attach, fe0->dvb.frontend, + + if (!dvb_attach(lnbh25_attach, fes[0]->dvb.frontend, &lnbh25_conf, &ndev->i2c[num].adap)) { dev_dbg(&ndev->pci_dev->dev, "%s(): unable to attach SEC frontend\n", __func__); goto frontend_detach; } + /* DVB-T/T2 frontend */ - fe1->dvb.frontend = dvb_attach(cxd2841er_attach_t, + fes[1]->dvb.frontend = dvb_attach(cxd2841er_attach_t_c, &demod_config, &ndev->i2c[num].adap); - if (fe1->dvb.frontend == NULL) { - dev_dbg(&ndev->pci_dev->dev, - "%s(): unable to attach DVB-T frontend\n", __func__); - goto frontend_detach; - } - fe1->dvb.frontend->id = 1; - ascot2e_conf.set_tuner_priv = &ndev->dma[num]; - if (!dvb_attach(ascot2e_attach, fe1->dvb.frontend, - &ascot2e_conf, &ndev->i2c[num].adap)) { - dev_dbg(&ndev->pci_dev->dev, - "%s(): unable to attach DVB-T tuner frontend\n", - __func__); - goto frontend_detach; - } - /* DVB-C/C2 frontend */ - fe2->dvb.frontend = dvb_attach(cxd2841er_attach_c, - &demod_config, &ndev->i2c[num].adap); - if (fe2->dvb.frontend == NULL) { + if (fes[1]->dvb.frontend == NULL) { dev_dbg(&ndev->pci_dev->dev, - "%s(): unable to attach DVB-C frontend\n", __func__); + "%s(): unable to attach Ter frontend\n", __func__); goto frontend_detach; } - fe2->dvb.frontend->id = 2; - if (!dvb_attach(ascot2e_attach, fe2->dvb.frontend, - &ascot2e_conf, &ndev->i2c[num].adap)) { - dev_dbg(&ndev->pci_dev->dev, - "%s(): unable to attach DVB-T/C tuner frontend\n", - __func__); - goto frontend_detach; + fes[1]->dvb.frontend->id = 1; + if (ndev->rev == NETUP_HW_REV_1_3) { + ascot2e_conf.set_tuner_priv = &ndev->dma[num]; + if (!dvb_attach(ascot2e_attach, fes[1]->dvb.frontend, + &ascot2e_conf, &ndev->i2c[num].adap)) { + dev_dbg(&ndev->pci_dev->dev, + "%s(): unable to attach Ter tuner frontend\n", + __func__); + goto frontend_detach; + } + } else { + helene_conf.set_tuner_priv = &ndev->dma[num]; + if (!dvb_attach(helene_attach, fes[1]->dvb.frontend, + &helene_conf, &ndev->i2c[num].adap)) { + dev_err(&ndev->pci_dev->dev, + "%s(): unable to attach HELENE Ter tuner frontend\n", + __func__); + goto frontend_detach; + } } if (vb2_dvb_register_bus(&ndev->frontends[num], @@ -730,7 +763,7 @@ static int netup_unidvb_request_mmio(struct pci_dev *pci_dev) static int netup_unidvb_request_modules(struct device *dev) { static const char * const modules[] = { - "lnbh25", "ascot2e", "horus3a", "cxd2841er", NULL + "lnbh25", "ascot2e", "horus3a", "cxd2841er", "helene", NULL }; const char * const *curr_mod = modules; int err; @@ -774,6 +807,16 @@ static int netup_unidvb_initdev(struct pci_dev *pci_dev, if (!ndev) goto dev_alloc_err; + /* detect hardware revision */ + if (pci_dev->device == NETUP_HW_REV_1_3) + ndev->rev = NETUP_HW_REV_1_3; + else + ndev->rev = NETUP_HW_REV_1_4; + + dev_info(&pci_dev->dev, + "%s(): board (0x%x) hardware revision 0x%x\n", + __func__, pci_dev->device, ndev->rev); + ndev->old_fw = old_firmware; ndev->wq = create_singlethread_workqueue(NETUP_UNIDVB_NAME); if (!ndev->wq) { @@ -972,7 +1015,8 @@ static void netup_unidvb_finidev(struct pci_dev *pci_dev) static struct pci_device_id netup_unidvb_pci_tbl[] = { - { PCI_DEVICE(0x1b55, 0x18f6) }, + { PCI_DEVICE(0x1b55, 0x18f6) }, /* hw rev. 1.3 */ + { PCI_DEVICE(0x1b55, 0x18f7) }, /* hw rev. 1.4 */ { 0, } }; MODULE_DEVICE_TABLE(pci, netup_unidvb_pci_tbl); diff --git a/drivers/media/pci/solo6x10/solo6x10-v4l2.c b/drivers/media/pci/solo6x10/solo6x10-v4l2.c index 721ff5320de7..953d6bf22320 100644 --- a/drivers/media/pci/solo6x10/solo6x10-v4l2.c +++ b/drivers/media/pci/solo6x10/solo6x10-v4l2.c @@ -386,26 +386,24 @@ static int solo_querycap(struct file *file, void *priv, static int solo_enum_ext_input(struct solo_dev *solo_dev, struct v4l2_input *input) { - static const char * const dispnames_1[] = { "4UP" }; - static const char * const dispnames_2[] = { "4UP-1", "4UP-2" }; - static const char * const dispnames_5[] = { - "4UP-1", "4UP-2", "4UP-3", "4UP-4", "16UP" - }; - const char * const *dispnames; - - if (input->index >= (solo_dev->nr_chans + solo_dev->nr_ext)) - return -EINVAL; - - if (solo_dev->nr_ext == 5) - dispnames = dispnames_5; - else if (solo_dev->nr_ext == 2) - dispnames = dispnames_2; - else - dispnames = dispnames_1; + int ext = input->index - solo_dev->nr_chans; + unsigned int nup, first; - snprintf(input->name, sizeof(input->name), "Multi %s", - dispnames[input->index - solo_dev->nr_chans]); + if (ext >= solo_dev->nr_ext) + return -EINVAL; + nup = (ext == 4) ? 16 : 4; + first = (ext & 3) << 2; /* first channel in the n-up */ + snprintf(input->name, sizeof(input->name), + "Multi %d-up (cameras %d-%d)", + nup, first + 1, first + nup); + /* Possible outputs: + * Multi 4-up (cameras 1-4) + * Multi 4-up (cameras 5-8) + * Multi 4-up (cameras 9-12) + * Multi 4-up (cameras 13-16) + * Multi 16-up (cameras 1-16) + */ return 0; } diff --git a/drivers/media/pci/tw686x/Kconfig b/drivers/media/pci/tw686x/Kconfig index fb8536974052..34ff37712313 100644 --- a/drivers/media/pci/tw686x/Kconfig +++ b/drivers/media/pci/tw686x/Kconfig @@ -3,6 +3,8 @@ config VIDEO_TW686X depends on PCI && VIDEO_DEV && VIDEO_V4L2 && SND depends on HAS_DMA select VIDEOBUF2_VMALLOC + select VIDEOBUF2_DMA_CONTIG + select VIDEOBUF2_DMA_SG select SND_PCM help Support for Intersil/Techwell TW686x-based frame grabber cards. diff --git a/drivers/media/pci/tw686x/tw686x-audio.c b/drivers/media/pci/tw686x/tw686x-audio.c index 91459ab715b2..96e444c49173 100644 --- a/drivers/media/pci/tw686x/tw686x-audio.c +++ b/drivers/media/pci/tw686x/tw686x-audio.c @@ -62,12 +62,22 @@ void tw686x_audio_irq(struct tw686x_dev *dev, unsigned long requests, } spin_unlock_irqrestore(&ac->lock, flags); + if (!done || !next) + continue; + /* + * Checking for a non-nil dma_desc[pb]->virt buffer is + * the same as checking for memcpy DMA mode. + */ desc = &ac->dma_descs[pb]; - if (done && next && desc->virt) { - memcpy(done->virt, desc->virt, desc->size); - ac->ptr = done->dma - ac->buf[0].dma; - snd_pcm_period_elapsed(ac->ss); + if (desc->virt) { + memcpy(done->virt, desc->virt, + dev->period_size); + } else { + u32 reg = pb ? ADMA_B_ADDR[ch] : ADMA_P_ADDR[ch]; + reg_write(dev, reg, next->dma); } + ac->ptr = done->dma - ac->buf[0].dma; + snd_pcm_period_elapsed(ac->ss); } } @@ -83,10 +93,9 @@ static int tw686x_pcm_hw_free(struct snd_pcm_substream *ss) } /* - * The audio device rate is global and shared among all - * capture channels. The driver makes no effort to prevent - * rate modifications. User is free change the rate, but it - * means changing the rate for all capture sub-devices. + * Audio parameters are global and shared among all + * capture channels. The driver prevents changes to + * the parameters if any audio channel is capturing. */ static const struct snd_pcm_hardware tw686x_capture_hw = { .info = (SNDRV_PCM_INFO_MMAP | @@ -99,9 +108,9 @@ static const struct snd_pcm_hardware tw686x_capture_hw = { .rate_max = 48000, .channels_min = 1, .channels_max = 1, - .buffer_bytes_max = TW686X_AUDIO_PAGE_MAX * TW686X_AUDIO_PAGE_SZ, - .period_bytes_min = TW686X_AUDIO_PAGE_SZ, - .period_bytes_max = TW686X_AUDIO_PAGE_SZ, + .buffer_bytes_max = TW686X_AUDIO_PAGE_MAX * AUDIO_DMA_SIZE_MAX, + .period_bytes_min = AUDIO_DMA_SIZE_MIN, + .period_bytes_max = AUDIO_DMA_SIZE_MAX, .periods_min = TW686X_AUDIO_PERIODS_MIN, .periods_max = TW686X_AUDIO_PERIODS_MAX, }; @@ -143,6 +152,14 @@ static int tw686x_pcm_prepare(struct snd_pcm_substream *ss) int i; spin_lock_irqsave(&dev->lock, flags); + /* + * Given the audio parameters are global (i.e. shared across + * DMA channels), we need to check new params are allowed. + */ + if (((dev->audio_rate != rt->rate) || + (dev->period_size != period_size)) && dev->audio_enabled) + goto err_audio_busy; + tw686x_disable_channel(dev, AUDIO_CHANNEL_OFFSET + ac->ch); spin_unlock_irqrestore(&dev->lock, flags); @@ -156,12 +173,21 @@ static int tw686x_pcm_prepare(struct snd_pcm_substream *ss) reg_write(dev, AUDIO_CONTROL2, reg); } - if (period_size != TW686X_AUDIO_PAGE_SZ || - rt->periods < TW686X_AUDIO_PERIODS_MIN || - rt->periods > TW686X_AUDIO_PERIODS_MAX) { - return -EINVAL; + if (dev->period_size != period_size) { + u32 reg; + + dev->period_size = period_size; + reg = reg_read(dev, AUDIO_CONTROL1); + reg &= ~(AUDIO_DMA_SIZE_MASK << AUDIO_DMA_SIZE_SHIFT); + reg |= period_size << AUDIO_DMA_SIZE_SHIFT; + + reg_write(dev, AUDIO_CONTROL1, reg); } + if (rt->periods < TW686X_AUDIO_PERIODS_MIN || + rt->periods > TW686X_AUDIO_PERIODS_MAX) + return -EINVAL; + spin_lock_irqsave(&ac->lock, flags); INIT_LIST_HEAD(&ac->buf_list); @@ -181,9 +207,19 @@ static int tw686x_pcm_prepare(struct snd_pcm_substream *ss) ac->curr_bufs[0] = p_buf; ac->curr_bufs[1] = b_buf; ac->ptr = 0; + + if (dev->dma_mode != TW686X_DMA_MODE_MEMCPY) { + reg_write(dev, ADMA_P_ADDR[ac->ch], p_buf->dma); + reg_write(dev, ADMA_B_ADDR[ac->ch], b_buf->dma); + } + spin_unlock_irqrestore(&ac->lock, flags); return 0; + +err_audio_busy: + spin_unlock_irqrestore(&dev->lock, flags); + return -EBUSY; } static int tw686x_pcm_trigger(struct snd_pcm_substream *ss, int cmd) @@ -197,6 +233,7 @@ static int tw686x_pcm_trigger(struct snd_pcm_substream *ss, int cmd) case SNDRV_PCM_TRIGGER_START: if (ac->curr_bufs[0] && ac->curr_bufs[1]) { spin_lock_irqsave(&dev->lock, flags); + dev->audio_enabled = 1; tw686x_enable_channel(dev, AUDIO_CHANNEL_OFFSET + ac->ch); spin_unlock_irqrestore(&dev->lock, flags); @@ -209,6 +246,7 @@ static int tw686x_pcm_trigger(struct snd_pcm_substream *ss, int cmd) break; case SNDRV_PCM_TRIGGER_STOP: spin_lock_irqsave(&dev->lock, flags); + dev->audio_enabled = 0; tw686x_disable_channel(dev, AUDIO_CHANNEL_OFFSET + ac->ch); spin_unlock_irqrestore(&dev->lock, flags); @@ -266,8 +304,8 @@ static int tw686x_snd_pcm_init(struct tw686x_dev *dev) return snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(dev->pci_dev), - TW686X_AUDIO_PAGE_MAX * TW686X_AUDIO_PAGE_SZ, - TW686X_AUDIO_PAGE_MAX * TW686X_AUDIO_PAGE_SZ); + TW686X_AUDIO_PAGE_MAX * AUDIO_DMA_SIZE_MAX, + TW686X_AUDIO_PAGE_MAX * AUDIO_DMA_SIZE_MAX); } static void tw686x_audio_dma_free(struct tw686x_dev *dev, @@ -290,11 +328,19 @@ static int tw686x_audio_dma_alloc(struct tw686x_dev *dev, { int pb; + /* + * In the memcpy DMA mode we allocate a consistent buffer + * and use it for the DMA capture. Otherwise, DMA + * acts on the ALSA buffers as received in pcm_prepare. + */ + if (dev->dma_mode != TW686X_DMA_MODE_MEMCPY) + return 0; + for (pb = 0; pb < 2; pb++) { u32 reg = pb ? ADMA_B_ADDR[ac->ch] : ADMA_P_ADDR[ac->ch]; void *virt; - virt = pci_alloc_consistent(dev->pci_dev, TW686X_AUDIO_PAGE_SZ, + virt = pci_alloc_consistent(dev->pci_dev, AUDIO_DMA_SIZE_MAX, &ac->dma_descs[pb].phys); if (!virt) { dev_err(&dev->pci_dev->dev, @@ -303,7 +349,7 @@ static int tw686x_audio_dma_alloc(struct tw686x_dev *dev, return -ENOMEM; } ac->dma_descs[pb].virt = virt; - ac->dma_descs[pb].size = TW686X_AUDIO_PAGE_SZ; + ac->dma_descs[pb].size = AUDIO_DMA_SIZE_MAX; reg_write(dev, reg, ac->dma_descs[pb].phys); } return 0; @@ -334,12 +380,8 @@ int tw686x_audio_init(struct tw686x_dev *dev) struct snd_card *card; int err, ch; - /* - * AUDIO_CONTROL1 - * DMA byte length [31:19] = 4096 (i.e. ALSA period) - * External audio enable [0] = enabled - */ - reg_write(dev, AUDIO_CONTROL1, 0x80000001); + /* Enable external audio */ + reg_write(dev, AUDIO_CONTROL1, BIT(0)); err = snd_card_new(&pci_dev->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, diff --git a/drivers/media/pci/tw686x/tw686x-core.c b/drivers/media/pci/tw686x/tw686x-core.c index cf53b0e97be2..71a0453b1af1 100644 --- a/drivers/media/pci/tw686x/tw686x-core.c +++ b/drivers/media/pci/tw686x/tw686x-core.c @@ -21,12 +21,14 @@ * under stress testings it has been found that the machine can * freeze completely if DMA registers are programmed while streaming * is active. - * This driver tries to access hardware registers as infrequently - * as possible by: - * i. allocating fixed DMA buffers and memcpy'ing into - * vmalloc'ed buffers - * ii. using a timer to mitigate the rate of DMA reset operations, - * on DMA channels error. + * + * Therefore, driver implements a dma_mode called 'memcpy' which + * avoids cycling the DMA buffers, and insteads allocates extra DMA buffers + * and then copies into vmalloc'ed user buffers. + * + * In addition to this, when streaming is on, the driver tries to access + * hardware registers as infrequently as possible. This is done by using + * a timer to limit the rate at which DMA is reset on DMA channels error. */ #include <linux/init.h> @@ -55,6 +57,42 @@ static u32 dma_interval = 0x00098968; module_param(dma_interval, int, 0444); MODULE_PARM_DESC(dma_interval, "Minimum time span for DMA interrupting host"); +static unsigned int dma_mode = TW686X_DMA_MODE_MEMCPY; +static const char *dma_mode_name(unsigned int mode) +{ + switch (mode) { + case TW686X_DMA_MODE_MEMCPY: + return "memcpy"; + case TW686X_DMA_MODE_CONTIG: + return "contig"; + case TW686X_DMA_MODE_SG: + return "sg"; + default: + return "unknown"; + } +} + +static int tw686x_dma_mode_get(char *buffer, struct kernel_param *kp) +{ + return sprintf(buffer, dma_mode_name(dma_mode)); +} + +static int tw686x_dma_mode_set(const char *val, struct kernel_param *kp) +{ + if (!strcasecmp(val, dma_mode_name(TW686X_DMA_MODE_MEMCPY))) + dma_mode = TW686X_DMA_MODE_MEMCPY; + else if (!strcasecmp(val, dma_mode_name(TW686X_DMA_MODE_CONTIG))) + dma_mode = TW686X_DMA_MODE_CONTIG; + else if (!strcasecmp(val, dma_mode_name(TW686X_DMA_MODE_SG))) + dma_mode = TW686X_DMA_MODE_SG; + else + return -EINVAL; + return 0; +} +module_param_call(dma_mode, tw686x_dma_mode_set, tw686x_dma_mode_get, + &dma_mode, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(dma_mode, "DMA operation mode (memcpy/contig/sg, default=memcpy)"); + void tw686x_disable_channel(struct tw686x_dev *dev, unsigned int channel) { u32 dma_en = reg_read(dev, DMA_CHANNEL_ENABLE); @@ -212,6 +250,7 @@ static int tw686x_probe(struct pci_dev *pci_dev, if (!dev) return -ENOMEM; dev->type = pci_id->driver_data; + dev->dma_mode = dma_mode; sprintf(dev->name, "tw%04X", pci_dev->device); dev->video_channels = kcalloc(max_channels(dev), @@ -228,9 +267,10 @@ static int tw686x_probe(struct pci_dev *pci_dev, goto free_video; } - pr_info("%s: PCI %s, IRQ %d, MMIO 0x%lx\n", dev->name, + pr_info("%s: PCI %s, IRQ %d, MMIO 0x%lx (%s mode)\n", dev->name, pci_name(pci_dev), pci_dev->irq, - (unsigned long)pci_resource_start(pci_dev, 0)); + (unsigned long)pci_resource_start(pci_dev, 0), + dma_mode_name(dma_mode)); dev->pci_dev = pci_dev; if (pci_enable_device(pci_dev)) { diff --git a/drivers/media/pci/tw686x/tw686x-regs.h b/drivers/media/pci/tw686x/tw686x-regs.h index fcef586a4c8c..15a956642ef4 100644 --- a/drivers/media/pci/tw686x/tw686x-regs.h +++ b/drivers/media/pci/tw686x/tw686x-regs.h @@ -105,6 +105,10 @@ 0x2d0, 0x2d1, 0x2d2, 0x2d3 }) #define SYS_MODE_DMA_SHIFT 13 +#define AUDIO_DMA_SIZE_SHIFT 19 +#define AUDIO_DMA_SIZE_MIN SZ_512 +#define AUDIO_DMA_SIZE_MAX SZ_4K +#define AUDIO_DMA_SIZE_MASK (SZ_8K - 1) #define DMA_CMD_ENABLE BIT(31) #define INT_STATUS_DMA_TOUT BIT(17) @@ -119,4 +123,9 @@ #define TW686X_STD_PAL_CN 5 #define TW686X_STD_PAL_60 6 +#define TW686X_FIELD_MODE 0x3 +#define TW686X_FRAME_MODE 0x2 +/* 0x1 is reserved */ +#define TW686X_SG_MODE 0x0 + #define TW686X_FIFO_ERROR(x) (x & ~(0xff)) diff --git a/drivers/media/pci/tw686x/tw686x-video.c b/drivers/media/pci/tw686x/tw686x-video.c index 253e10823ba3..0e839f617e03 100644 --- a/drivers/media/pci/tw686x/tw686x-video.c +++ b/drivers/media/pci/tw686x/tw686x-video.c @@ -19,6 +19,8 @@ #include <linux/slab.h> #include <media/v4l2-common.h> #include <media/v4l2-event.h> +#include <media/videobuf2-dma-contig.h> +#include <media/videobuf2-dma-sg.h> #include <media/videobuf2-vmalloc.h> #include "tw686x.h" #include "tw686x-regs.h" @@ -27,6 +29,10 @@ #define TW686X_VIDEO_WIDTH 720 #define TW686X_VIDEO_HEIGHT(id) ((id & V4L2_STD_525_60) ? 480 : 576) +#define TW686X_MAX_SG_ENTRY_SIZE 4096 +#define TW686X_MAX_SG_DESC_COUNT 256 /* PAL 720x576 needs 203 4-KB pages */ +#define TW686X_SG_TABLE_SIZE (TW686X_MAX_SG_DESC_COUNT * sizeof(struct tw686x_sg_desc)) + static const struct tw686x_format formats[] = { { .fourcc = V4L2_PIX_FMT_UYVY, @@ -43,6 +49,326 @@ static const struct tw686x_format formats[] = { } }; +static void tw686x_buf_done(struct tw686x_video_channel *vc, + unsigned int pb) +{ + struct tw686x_dma_desc *desc = &vc->dma_descs[pb]; + struct tw686x_dev *dev = vc->dev; + struct vb2_v4l2_buffer *vb; + struct vb2_buffer *vb2_buf; + + if (vc->curr_bufs[pb]) { + vb = &vc->curr_bufs[pb]->vb; + + vb->field = dev->dma_ops->field; + vb->sequence = vc->sequence++; + vb2_buf = &vb->vb2_buf; + + if (dev->dma_mode == TW686X_DMA_MODE_MEMCPY) + memcpy(vb2_plane_vaddr(vb2_buf, 0), desc->virt, + desc->size); + vb2_buf->timestamp = ktime_get_ns(); + vb2_buffer_done(vb2_buf, VB2_BUF_STATE_DONE); + } + + vc->pb = !pb; +} + +/* + * We can call this even when alloc_dma failed for the given channel + */ +static void tw686x_memcpy_dma_free(struct tw686x_video_channel *vc, + unsigned int pb) +{ + struct tw686x_dma_desc *desc = &vc->dma_descs[pb]; + struct tw686x_dev *dev = vc->dev; + struct pci_dev *pci_dev; + unsigned long flags; + + /* Check device presence. Shouldn't really happen! */ + spin_lock_irqsave(&dev->lock, flags); + pci_dev = dev->pci_dev; + spin_unlock_irqrestore(&dev->lock, flags); + if (!pci_dev) { + WARN(1, "trying to deallocate on missing device\n"); + return; + } + + if (desc->virt) { + pci_free_consistent(dev->pci_dev, desc->size, + desc->virt, desc->phys); + desc->virt = NULL; + } +} + +static int tw686x_memcpy_dma_alloc(struct tw686x_video_channel *vc, + unsigned int pb) +{ + struct tw686x_dev *dev = vc->dev; + u32 reg = pb ? VDMA_B_ADDR[vc->ch] : VDMA_P_ADDR[vc->ch]; + unsigned int len; + void *virt; + + WARN(vc->dma_descs[pb].virt, + "Allocating buffer but previous still here\n"); + + len = (vc->width * vc->height * vc->format->depth) >> 3; + virt = pci_alloc_consistent(dev->pci_dev, len, + &vc->dma_descs[pb].phys); + if (!virt) { + v4l2_err(&dev->v4l2_dev, + "dma%d: unable to allocate %s-buffer\n", + vc->ch, pb ? "B" : "P"); + return -ENOMEM; + } + vc->dma_descs[pb].size = len; + vc->dma_descs[pb].virt = virt; + reg_write(dev, reg, vc->dma_descs[pb].phys); + + return 0; +} + +static void tw686x_memcpy_buf_refill(struct tw686x_video_channel *vc, + unsigned int pb) +{ + struct tw686x_v4l2_buf *buf; + + while (!list_empty(&vc->vidq_queued)) { + + buf = list_first_entry(&vc->vidq_queued, + struct tw686x_v4l2_buf, list); + list_del(&buf->list); + + vc->curr_bufs[pb] = buf; + return; + } + vc->curr_bufs[pb] = NULL; +} + +const struct tw686x_dma_ops memcpy_dma_ops = { + .alloc = tw686x_memcpy_dma_alloc, + .free = tw686x_memcpy_dma_free, + .buf_refill = tw686x_memcpy_buf_refill, + .mem_ops = &vb2_vmalloc_memops, + .hw_dma_mode = TW686X_FRAME_MODE, + .field = V4L2_FIELD_INTERLACED, +}; + +static void tw686x_contig_buf_refill(struct tw686x_video_channel *vc, + unsigned int pb) +{ + struct tw686x_v4l2_buf *buf; + + while (!list_empty(&vc->vidq_queued)) { + u32 reg = pb ? VDMA_B_ADDR[vc->ch] : VDMA_P_ADDR[vc->ch]; + dma_addr_t phys; + + buf = list_first_entry(&vc->vidq_queued, + struct tw686x_v4l2_buf, list); + list_del(&buf->list); + + phys = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0); + reg_write(vc->dev, reg, phys); + + buf->vb.vb2_buf.state = VB2_BUF_STATE_ACTIVE; + vc->curr_bufs[pb] = buf; + return; + } + vc->curr_bufs[pb] = NULL; +} + +static void tw686x_contig_cleanup(struct tw686x_dev *dev) +{ + vb2_dma_contig_cleanup_ctx(dev->alloc_ctx); +} + +static int tw686x_contig_setup(struct tw686x_dev *dev) +{ + dev->alloc_ctx = vb2_dma_contig_init_ctx(&dev->pci_dev->dev); + if (IS_ERR(dev->alloc_ctx)) { + dev_err(&dev->pci_dev->dev, "unable to init DMA context\n"); + return PTR_ERR(dev->alloc_ctx); + } + return 0; +} + +const struct tw686x_dma_ops contig_dma_ops = { + .setup = tw686x_contig_setup, + .cleanup = tw686x_contig_cleanup, + .buf_refill = tw686x_contig_buf_refill, + .mem_ops = &vb2_dma_contig_memops, + .hw_dma_mode = TW686X_FRAME_MODE, + .field = V4L2_FIELD_INTERLACED, +}; + +static int tw686x_sg_desc_fill(struct tw686x_sg_desc *descs, + struct tw686x_v4l2_buf *buf, + unsigned int buf_len) +{ + struct sg_table *vbuf = vb2_dma_sg_plane_desc(&buf->vb.vb2_buf, 0); + unsigned int len, entry_len; + struct scatterlist *sg; + int i, count; + + /* Clear the scatter-gather table */ + memset(descs, 0, TW686X_SG_TABLE_SIZE); + + count = 0; + for_each_sg(vbuf->sgl, sg, vbuf->nents, i) { + dma_addr_t phys = sg_dma_address(sg); + len = sg_dma_len(sg); + + while (len && buf_len) { + + if (count == TW686X_MAX_SG_DESC_COUNT) + return -ENOMEM; + + entry_len = min_t(unsigned int, len, + TW686X_MAX_SG_ENTRY_SIZE); + entry_len = min_t(unsigned int, entry_len, buf_len); + descs[count].phys = cpu_to_le32(phys); + descs[count++].flags_length = + cpu_to_le32(BIT(30) | entry_len); + phys += entry_len; + len -= entry_len; + buf_len -= entry_len; + } + + if (!buf_len) + return 0; + } + + return -ENOMEM; +} + +static void tw686x_sg_buf_refill(struct tw686x_video_channel *vc, + unsigned int pb) +{ + struct tw686x_dev *dev = vc->dev; + struct tw686x_v4l2_buf *buf; + + while (!list_empty(&vc->vidq_queued)) { + unsigned int buf_len; + + buf = list_first_entry(&vc->vidq_queued, + struct tw686x_v4l2_buf, list); + list_del(&buf->list); + + buf_len = (vc->width * vc->height * vc->format->depth) >> 3; + if (tw686x_sg_desc_fill(vc->sg_descs[pb], buf, buf_len)) { + v4l2_err(&dev->v4l2_dev, + "dma%d: unable to fill %s-buffer\n", + vc->ch, pb ? "B" : "P"); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + continue; + } + + buf->vb.vb2_buf.state = VB2_BUF_STATE_ACTIVE; + vc->curr_bufs[pb] = buf; + return; + } + + vc->curr_bufs[pb] = NULL; +} + +static void tw686x_sg_dma_free(struct tw686x_video_channel *vc, + unsigned int pb) +{ + struct tw686x_dma_desc *desc = &vc->dma_descs[pb]; + struct tw686x_dev *dev = vc->dev; + + if (desc->size) { + pci_free_consistent(dev->pci_dev, desc->size, + desc->virt, desc->phys); + desc->virt = NULL; + } + + vc->sg_descs[pb] = NULL; +} + +static int tw686x_sg_dma_alloc(struct tw686x_video_channel *vc, + unsigned int pb) +{ + struct tw686x_dma_desc *desc = &vc->dma_descs[pb]; + struct tw686x_dev *dev = vc->dev; + u32 reg = pb ? DMA_PAGE_TABLE1_ADDR[vc->ch] : + DMA_PAGE_TABLE0_ADDR[vc->ch]; + void *virt; + + if (desc->size) { + + virt = pci_alloc_consistent(dev->pci_dev, desc->size, + &desc->phys); + if (!virt) { + v4l2_err(&dev->v4l2_dev, + "dma%d: unable to allocate %s-buffer\n", + vc->ch, pb ? "B" : "P"); + return -ENOMEM; + } + desc->virt = virt; + reg_write(dev, reg, desc->phys); + } else { + virt = dev->video_channels[0].dma_descs[pb].virt + + vc->ch * TW686X_SG_TABLE_SIZE; + } + + vc->sg_descs[pb] = virt; + return 0; +} + +static void tw686x_sg_cleanup(struct tw686x_dev *dev) +{ + vb2_dma_sg_cleanup_ctx(dev->alloc_ctx); +} + +static int tw686x_sg_setup(struct tw686x_dev *dev) +{ + unsigned int sg_table_size, pb, ch, channels; + + dev->alloc_ctx = vb2_dma_sg_init_ctx(&dev->pci_dev->dev); + if (IS_ERR(dev->alloc_ctx)) { + dev_err(&dev->pci_dev->dev, "unable to init DMA context\n"); + return PTR_ERR(dev->alloc_ctx); + } + + if (is_second_gen(dev)) { + /* + * TW6865/TW6869: each channel needs a pair of + * P-B descriptor tables. + */ + channels = max_channels(dev); + sg_table_size = TW686X_SG_TABLE_SIZE; + } else { + /* + * TW6864/TW6868: we need to allocate a pair of + * P-B descriptor tables, common for all channels. + * Each table will be bigger than 4 KB. + */ + channels = 1; + sg_table_size = max_channels(dev) * TW686X_SG_TABLE_SIZE; + } + + for (ch = 0; ch < channels; ch++) { + struct tw686x_video_channel *vc = &dev->video_channels[ch]; + + for (pb = 0; pb < 2; pb++) + vc->dma_descs[pb].size = sg_table_size; + } + + return 0; +} + +const struct tw686x_dma_ops sg_dma_ops = { + .setup = tw686x_sg_setup, + .cleanup = tw686x_sg_cleanup, + .alloc = tw686x_sg_dma_alloc, + .free = tw686x_sg_dma_free, + .buf_refill = tw686x_sg_buf_refill, + .mem_ops = &vb2_dma_sg_memops, + .hw_dma_mode = TW686X_SG_MODE, + .field = V4L2_FIELD_SEQ_TB, +}; + static unsigned int tw686x_fields_map(v4l2_std_id std, unsigned int fps) { static const unsigned int map[15] = { @@ -123,6 +449,7 @@ static int tw686x_queue_setup(struct vb2_queue *vq, return 0; } + alloc_ctxs[0] = vc->dev->alloc_ctx; sizes[0] = szimage; *nplanes = 1; return 0; @@ -152,75 +479,6 @@ static void tw686x_buf_queue(struct vb2_buffer *vb) spin_unlock_irqrestore(&vc->qlock, flags); } -/* - * We can call this even when alloc_dma failed for the given channel - */ -static void tw686x_free_dma(struct tw686x_video_channel *vc, unsigned int pb) -{ - struct tw686x_dma_desc *desc = &vc->dma_descs[pb]; - struct tw686x_dev *dev = vc->dev; - struct pci_dev *pci_dev; - unsigned long flags; - - /* Check device presence. Shouldn't really happen! */ - spin_lock_irqsave(&dev->lock, flags); - pci_dev = dev->pci_dev; - spin_unlock_irqrestore(&dev->lock, flags); - if (!pci_dev) { - WARN(1, "trying to deallocate on missing device\n"); - return; - } - - if (desc->virt) { - pci_free_consistent(dev->pci_dev, desc->size, - desc->virt, desc->phys); - desc->virt = NULL; - } -} - -static int tw686x_alloc_dma(struct tw686x_video_channel *vc, unsigned int pb) -{ - struct tw686x_dev *dev = vc->dev; - u32 reg = pb ? VDMA_B_ADDR[vc->ch] : VDMA_P_ADDR[vc->ch]; - unsigned int len; - void *virt; - - WARN(vc->dma_descs[pb].virt, - "Allocating buffer but previous still here\n"); - - len = (vc->width * vc->height * vc->format->depth) >> 3; - virt = pci_alloc_consistent(dev->pci_dev, len, - &vc->dma_descs[pb].phys); - if (!virt) { - v4l2_err(&dev->v4l2_dev, - "dma%d: unable to allocate %s-buffer\n", - vc->ch, pb ? "B" : "P"); - return -ENOMEM; - } - vc->dma_descs[pb].size = len; - vc->dma_descs[pb].virt = virt; - reg_write(dev, reg, vc->dma_descs[pb].phys); - - return 0; -} - -static void tw686x_buffer_refill(struct tw686x_video_channel *vc, - unsigned int pb) -{ - struct tw686x_v4l2_buf *buf; - - while (!list_empty(&vc->vidq_queued)) { - - buf = list_first_entry(&vc->vidq_queued, - struct tw686x_v4l2_buf, list); - list_del(&buf->list); - - vc->curr_bufs[pb] = buf; - return; - } - vc->curr_bufs[pb] = NULL; -} - static void tw686x_clear_queue(struct tw686x_video_channel *vc, enum vb2_buffer_state state) { @@ -262,7 +520,8 @@ static int tw686x_start_streaming(struct vb2_queue *vq, unsigned int count) spin_lock_irqsave(&vc->qlock, flags); /* Sanity check */ - if (!vc->dma_descs[0].virt || !vc->dma_descs[1].virt) { + if (dev->dma_mode == TW686X_DMA_MODE_MEMCPY && + (!vc->dma_descs[0].virt || !vc->dma_descs[1].virt)) { spin_unlock_irqrestore(&vc->qlock, flags); v4l2_err(&dev->v4l2_dev, "video%d: refusing to start without DMA buffers\n", @@ -272,7 +531,7 @@ static int tw686x_start_streaming(struct vb2_queue *vq, unsigned int count) } for (pb = 0; pb < 2; pb++) - tw686x_buffer_refill(vc, pb); + dev->dma_ops->buf_refill(vc, pb); spin_unlock_irqrestore(&vc->qlock, flags); vc->sequence = 0; @@ -375,10 +634,11 @@ static int tw686x_g_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { struct tw686x_video_channel *vc = video_drvdata(file); + struct tw686x_dev *dev = vc->dev; f->fmt.pix.width = vc->width; f->fmt.pix.height = vc->height; - f->fmt.pix.field = V4L2_FIELD_INTERLACED; + f->fmt.pix.field = dev->dma_ops->field; f->fmt.pix.pixelformat = vc->format->fourcc; f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; f->fmt.pix.bytesperline = (f->fmt.pix.width * vc->format->depth) / 8; @@ -390,6 +650,7 @@ static int tw686x_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { struct tw686x_video_channel *vc = video_drvdata(file); + struct tw686x_dev *dev = vc->dev; unsigned int video_height = TW686X_VIDEO_HEIGHT(vc->video_standard); const struct tw686x_format *format; @@ -412,7 +673,7 @@ static int tw686x_try_fmt_vid_cap(struct file *file, void *priv, f->fmt.pix.bytesperline = (f->fmt.pix.width * format->depth) / 8; f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; - f->fmt.pix.field = V4L2_FIELD_INTERLACED; + f->fmt.pix.field = dev->dma_ops->field; return 0; } @@ -421,6 +682,7 @@ static int tw686x_s_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { struct tw686x_video_channel *vc = video_drvdata(file); + struct tw686x_dev *dev = vc->dev; u32 val, width, line_width, height; unsigned long bitsperframe; int err, pb; @@ -438,15 +700,16 @@ static int tw686x_s_fmt_vid_cap(struct file *file, void *priv, vc->height = f->fmt.pix.height; /* We need new DMA buffers if the framesize has changed */ - if (bitsperframe != vc->width * vc->height * vc->format->depth) { + if (dev->dma_ops->alloc && + bitsperframe != vc->width * vc->height * vc->format->depth) { for (pb = 0; pb < 2; pb++) - tw686x_free_dma(vc, pb); + dev->dma_ops->free(vc, pb); for (pb = 0; pb < 2; pb++) { - err = tw686x_alloc_dma(vc, pb); + err = dev->dma_ops->alloc(vc, pb); if (err) { if (pb > 0) - tw686x_free_dma(vc, 0); + dev->dma_ops->free(vc, 0); return err; } } @@ -464,6 +727,19 @@ static int tw686x_s_fmt_vid_cap(struct file *file, void *priv, else val &= ~BIT(24); + val &= ~0x7ffff; + + /* Program the DMA scatter-gather */ + if (dev->dma_mode == TW686X_DMA_MODE_SG) { + u32 start_idx, end_idx; + + start_idx = is_second_gen(dev) ? + 0 : vc->ch * TW686X_MAX_SG_DESC_COUNT; + end_idx = start_idx + TW686X_MAX_SG_DESC_COUNT - 1; + + val |= (end_idx << 10) | start_idx; + } + val &= ~(0x7 << 20); val |= vc->format->mode << 20; reg_write(vc->dev, VDMA_CHANNEL_CONFIG[vc->ch], val); @@ -713,26 +989,11 @@ static const struct v4l2_ioctl_ops tw686x_video_ioctl_ops = { .vidioc_unsubscribe_event = v4l2_event_unsubscribe, }; -static void tw686x_buffer_copy(struct tw686x_video_channel *vc, - unsigned int pb, struct vb2_v4l2_buffer *vb) -{ - struct tw686x_dma_desc *desc = &vc->dma_descs[pb]; - struct vb2_buffer *vb2_buf = &vb->vb2_buf; - - vb->field = V4L2_FIELD_INTERLACED; - vb->sequence = vc->sequence++; - - memcpy(vb2_plane_vaddr(vb2_buf, 0), desc->virt, desc->size); - vb2_buf->timestamp = ktime_get_ns(); - vb2_buffer_done(vb2_buf, VB2_BUF_STATE_DONE); -} - void tw686x_video_irq(struct tw686x_dev *dev, unsigned long requests, unsigned int pb_status, unsigned int fifo_status, unsigned int *reset_ch) { struct tw686x_video_channel *vc; - struct vb2_v4l2_buffer *vb; unsigned long flags; unsigned int ch, pb; @@ -781,14 +1042,9 @@ void tw686x_video_irq(struct tw686x_dev *dev, unsigned long requests, continue; } - /* handle video stream */ spin_lock_irqsave(&vc->qlock, flags); - if (vc->curr_bufs[pb]) { - vb = &vc->curr_bufs[pb]->vb; - tw686x_buffer_copy(vc, pb, vb); - } - vc->pb = !pb; - tw686x_buffer_refill(vc, pb); + tw686x_buf_done(vc, pb); + dev->dma_ops->buf_refill(vc, pb); spin_unlock_irqrestore(&vc->qlock, flags); } } @@ -803,9 +1059,13 @@ void tw686x_video_free(struct tw686x_dev *dev) if (vc->device) video_unregister_device(vc->device); - for (pb = 0; pb < 2; pb++) - tw686x_free_dma(vc, pb); + if (dev->dma_ops->free) + for (pb = 0; pb < 2; pb++) + dev->dma_ops->free(vc, pb); } + + if (dev->dma_ops->cleanup) + dev->dma_ops->cleanup(dev); } int tw686x_video_init(struct tw686x_dev *dev) @@ -813,10 +1073,25 @@ int tw686x_video_init(struct tw686x_dev *dev) unsigned int ch, val, pb; int err; + if (dev->dma_mode == TW686X_DMA_MODE_MEMCPY) + dev->dma_ops = &memcpy_dma_ops; + else if (dev->dma_mode == TW686X_DMA_MODE_CONTIG) + dev->dma_ops = &contig_dma_ops; + else if (dev->dma_mode == TW686X_DMA_MODE_SG) + dev->dma_ops = &sg_dma_ops; + else + return -EINVAL; + err = v4l2_device_register(&dev->pci_dev->dev, &dev->v4l2_dev); if (err) return err; + if (dev->dma_ops->setup) { + err = dev->dma_ops->setup(dev); + if (err) + return err; + } + for (ch = 0; ch < max_channels(dev); ch++) { struct tw686x_video_channel *vc = &dev->video_channels[ch]; struct video_device *vdev; @@ -842,10 +1117,12 @@ int tw686x_video_init(struct tw686x_dev *dev) reg_write(dev, HACTIVE_LO[ch], 0xd0); reg_write(dev, VIDEO_SIZE[ch], 0); - for (pb = 0; pb < 2; pb++) { - err = tw686x_alloc_dma(vc, pb); - if (err) - goto error; + if (dev->dma_ops->alloc) { + for (pb = 0; pb < 2; pb++) { + err = dev->dma_ops->alloc(vc, pb); + if (err) + goto error; + } } vc->vidq.io_modes = VB2_READ | VB2_MMAP | VB2_DMABUF; @@ -853,7 +1130,7 @@ int tw686x_video_init(struct tw686x_dev *dev) vc->vidq.drv_priv = vc; vc->vidq.buf_struct_size = sizeof(struct tw686x_v4l2_buf); vc->vidq.ops = &tw686x_video_qops; - vc->vidq.mem_ops = &vb2_vmalloc_memops; + vc->vidq.mem_ops = dev->dma_ops->mem_ops; vc->vidq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; vc->vidq.min_buffers_needed = 2; vc->vidq.lock = &vc->vb_mutex; @@ -915,10 +1192,9 @@ int tw686x_video_init(struct tw686x_dev *dev) vc->num = vdev->num; } - /* Set DMA frame mode on all channels. Only supported mode for now. */ val = TW686X_DEF_PHASE_REF; for (ch = 0; ch < max_channels(dev); ch++) - val |= TW686X_FRAME_MODE << (16 + ch * 2); + val |= dev->dma_ops->hw_dma_mode << (16 + ch * 2); reg_write(dev, PHASE_REF, val); reg_write(dev, MISC2[0], 0xe7); diff --git a/drivers/media/pci/tw686x/tw686x.h b/drivers/media/pci/tw686x/tw686x.h index 44b5755acf02..35d7bc94f78f 100644 --- a/drivers/media/pci/tw686x/tw686x.h +++ b/drivers/media/pci/tw686x/tw686x.h @@ -27,16 +27,14 @@ #define TYPE_SECOND_GEN 0x10 #define TW686X_DEF_PHASE_REF 0x1518 -#define TW686X_FIELD_MODE 0x3 -#define TW686X_FRAME_MODE 0x2 -/* 0x1 is reserved */ -#define TW686X_SG_MODE 0x0 - -#define TW686X_AUDIO_PAGE_SZ 4096 #define TW686X_AUDIO_PAGE_MAX 16 #define TW686X_AUDIO_PERIODS_MIN 2 #define TW686X_AUDIO_PERIODS_MAX TW686X_AUDIO_PAGE_MAX +#define TW686X_DMA_MODE_MEMCPY 0 +#define TW686X_DMA_MODE_CONTIG 1 +#define TW686X_DMA_MODE_SG 2 + struct tw686x_format { char *name; unsigned int fourcc; @@ -50,6 +48,12 @@ struct tw686x_dma_desc { unsigned int size; }; +struct tw686x_sg_desc { + /* 3 MSBits for flags, 13 LSBits for length */ + __le32 flags_length; + __le32 phys; +}; + struct tw686x_audio_buf { dma_addr_t dma; void *virt; @@ -82,6 +86,7 @@ struct tw686x_video_channel { struct video_device *device; struct tw686x_v4l2_buf *curr_bufs[2]; struct tw686x_dma_desc dma_descs[2]; + struct tw686x_sg_desc *sg_descs[2]; struct v4l2_ctrl_handler ctrl_handler; const struct tw686x_format *format; @@ -99,6 +104,17 @@ struct tw686x_video_channel { bool no_signal; }; +struct tw686x_dma_ops { + int (*setup)(struct tw686x_dev *dev); + void (*cleanup)(struct tw686x_dev *dev); + int (*alloc)(struct tw686x_video_channel *vc, unsigned int pb); + void (*free)(struct tw686x_video_channel *vc, unsigned int pb); + void (*buf_refill)(struct tw686x_video_channel *vc, unsigned int pb); + const struct vb2_mem_ops *mem_ops; + enum v4l2_field field; + u32 hw_dma_mode; +}; + /** * struct tw686x_dev - global device status * @lock: spinlock controlling access to the @@ -112,15 +128,20 @@ struct tw686x_dev { char name[32]; unsigned int type; + unsigned int dma_mode; struct pci_dev *pci_dev; __u32 __iomem *mmio; void *alloc_ctx; + const struct tw686x_dma_ops *dma_ops; struct tw686x_video_channel *video_channels; struct tw686x_audio_channel *audio_channels; - int audio_rate; /* per-device value */ + /* Per-device audio parameters */ + int audio_rate; + int period_size; + int audio_enabled; struct timer_list dma_delay_timer; u32 pending_dma_en; /* must be protected by lock */ @@ -143,6 +164,12 @@ static inline unsigned int max_channels(struct tw686x_dev *dev) return dev->type & TYPE_MAX_CHANNELS; /* 4 or 8 channels */ } +static inline unsigned is_second_gen(struct tw686x_dev *dev) +{ + /* each channel has its own DMA SG table */ + return dev->type & TYPE_SECOND_GEN; +} + void tw686x_enable_channel(struct tw686x_dev *dev, unsigned int channel); void tw686x_disable_channel(struct tw686x_dev *dev, unsigned int channel); diff --git a/drivers/media/pci/zoran/zr36016.c b/drivers/media/pci/zoran/zr36016.c index b87ddba8608f..c12ca9f96bac 100644 --- a/drivers/media/pci/zoran/zr36016.c +++ b/drivers/media/pci/zoran/zr36016.c @@ -246,10 +246,6 @@ static int zr36016_pushit (struct zr36016 *ptr, //TODO// ========================================================================= */ -// needed offset values PAL NTSC SECAM -static const int zr016_xoff[] = { 20, 20, 20 }; -static const int zr016_yoff[] = { 8, 9, 7 }; - static void zr36016_init (struct zr36016 *ptr) { diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig index 84e041c0a70e..382f3937379e 100644 --- a/drivers/media/platform/Kconfig +++ b/drivers/media/platform/Kconfig @@ -110,6 +110,7 @@ source "drivers/media/platform/exynos4-is/Kconfig" source "drivers/media/platform/s5p-tv/Kconfig" source "drivers/media/platform/am437x/Kconfig" source "drivers/media/platform/xilinx/Kconfig" +source "drivers/media/platform/rcar-vin/Kconfig" config VIDEO_TI_CAL tristate "TI CAL (Camera Adaptation Layer) driver" diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile index bbb7bd1eb268..99cf31542f54 100644 --- a/drivers/media/platform/Makefile +++ b/drivers/media/platform/Makefile @@ -55,4 +55,6 @@ obj-$(CONFIG_VIDEO_AM437X_VPFE) += am437x/ obj-$(CONFIG_VIDEO_XILINX) += xilinx/ +obj-$(CONFIG_VIDEO_RCAR_VIN) += rcar-vin/ + ccflags-y += -I$(srctree)/drivers/media/i2c diff --git a/drivers/media/platform/exynos-gsc/gsc-core.c b/drivers/media/platform/exynos-gsc/gsc-core.c index c04973669a47..c9d2009c2476 100644 --- a/drivers/media/platform/exynos-gsc/gsc-core.c +++ b/drivers/media/platform/exynos-gsc/gsc-core.c @@ -1124,6 +1124,7 @@ static int gsc_probe(struct platform_device *pdev) goto err_m2m; /* Initialize continious memory allocator */ + vb2_dma_contig_set_max_seg_size(dev, DMA_BIT_MASK(32)); gsc->alloc_ctx = vb2_dma_contig_init_ctx(dev); if (IS_ERR(gsc->alloc_ctx)) { ret = PTR_ERR(gsc->alloc_ctx); @@ -1153,6 +1154,7 @@ static int gsc_remove(struct platform_device *pdev) v4l2_device_unregister(&gsc->v4l2_dev); vb2_dma_contig_cleanup_ctx(gsc->alloc_ctx); + vb2_dma_contig_clear_max_seg_size(&pdev->dev); pm_runtime_disable(&pdev->dev); gsc_clk_put(gsc); diff --git a/drivers/media/platform/exynos4-is/fimc-core.c b/drivers/media/platform/exynos4-is/fimc-core.c index b1c1cea82a27..368f44f24d4c 100644 --- a/drivers/media/platform/exynos4-is/fimc-core.c +++ b/drivers/media/platform/exynos4-is/fimc-core.c @@ -1019,6 +1019,7 @@ static int fimc_probe(struct platform_device *pdev) } /* Initialize contiguous memory allocator */ + vb2_dma_contig_set_max_seg_size(dev, DMA_BIT_MASK(32)); fimc->alloc_ctx = vb2_dma_contig_init_ctx(dev); if (IS_ERR(fimc->alloc_ctx)) { ret = PTR_ERR(fimc->alloc_ctx); @@ -1124,6 +1125,7 @@ static int fimc_remove(struct platform_device *pdev) fimc_unregister_capture_subdev(fimc); vb2_dma_contig_cleanup_ctx(fimc->alloc_ctx); + vb2_dma_contig_clear_max_seg_size(&pdev->dev); clk_disable(fimc->clock[CLK_BUS]); fimc_clk_put(fimc); diff --git a/drivers/media/platform/exynos4-is/fimc-is.c b/drivers/media/platform/exynos4-is/fimc-is.c index 979c388ebf60..bd98b56318b7 100644 --- a/drivers/media/platform/exynos4-is/fimc-is.c +++ b/drivers/media/platform/exynos4-is/fimc-is.c @@ -847,6 +847,7 @@ static int fimc_is_probe(struct platform_device *pdev) if (ret < 0) goto err_pm; + vb2_dma_contig_set_max_seg_size(dev, DMA_BIT_MASK(32)); is->alloc_ctx = vb2_dma_contig_init_ctx(dev); if (IS_ERR(is->alloc_ctx)) { ret = PTR_ERR(is->alloc_ctx); @@ -940,6 +941,7 @@ static int fimc_is_remove(struct platform_device *pdev) free_irq(is->irq, is); fimc_is_unregister_subdevs(is); vb2_dma_contig_cleanup_ctx(is->alloc_ctx); + vb2_dma_contig_clear_max_seg_size(dev); fimc_is_put_clocks(is); fimc_is_debugfs_remove(is); release_firmware(is->fw.f_w); diff --git a/drivers/media/platform/exynos4-is/fimc-lite.c b/drivers/media/platform/exynos4-is/fimc-lite.c index dc1b929f7a33..27cb620cb714 100644 --- a/drivers/media/platform/exynos4-is/fimc-lite.c +++ b/drivers/media/platform/exynos4-is/fimc-lite.c @@ -1551,6 +1551,7 @@ static int fimc_lite_probe(struct platform_device *pdev) goto err_sd; } + vb2_dma_contig_set_max_seg_size(dev, DMA_BIT_MASK(32)); fimc->alloc_ctx = vb2_dma_contig_init_ctx(dev); if (IS_ERR(fimc->alloc_ctx)) { ret = PTR_ERR(fimc->alloc_ctx); @@ -1652,6 +1653,7 @@ static int fimc_lite_remove(struct platform_device *pdev) pm_runtime_set_suspended(dev); fimc_lite_unregister_capture_subdev(fimc); vb2_dma_contig_cleanup_ctx(fimc->alloc_ctx); + vb2_dma_contig_clear_max_seg_size(dev); fimc_lite_clk_put(fimc); dev_info(dev, "Driver unloaded\n"); diff --git a/drivers/media/platform/exynos4-is/mipi-csis.c b/drivers/media/platform/exynos4-is/mipi-csis.c index bf954424e7be..86e681daa89d 100644 --- a/drivers/media/platform/exynos4-is/mipi-csis.c +++ b/drivers/media/platform/exynos4-is/mipi-csis.c @@ -649,23 +649,6 @@ static int s5pcsis_log_status(struct v4l2_subdev *sd) return 0; } -static int s5pcsis_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) -{ - struct v4l2_mbus_framefmt *format = v4l2_subdev_get_try_format(sd, fh->pad, 0); - - format->colorspace = V4L2_COLORSPACE_JPEG; - format->code = s5pcsis_formats[0].code; - format->width = S5PCSIS_DEF_PIX_WIDTH; - format->height = S5PCSIS_DEF_PIX_HEIGHT; - format->field = V4L2_FIELD_NONE; - - return 0; -} - -static const struct v4l2_subdev_internal_ops s5pcsis_sd_internal_ops = { - .open = s5pcsis_open, -}; - static struct v4l2_subdev_core_ops s5pcsis_core_ops = { .s_power = s5pcsis_s_power, .log_status = s5pcsis_log_status, diff --git a/drivers/media/platform/rcar-vin/Kconfig b/drivers/media/platform/rcar-vin/Kconfig new file mode 100644 index 000000000000..b2ff2d4e8bb1 --- /dev/null +++ b/drivers/media/platform/rcar-vin/Kconfig @@ -0,0 +1,11 @@ +config VIDEO_RCAR_VIN + tristate "R-Car Video Input (VIN) Driver" + depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API && OF && HAS_DMA + depends on ARCH_RENESAS || COMPILE_TEST + select VIDEOBUF2_DMA_CONTIG + ---help--- + Support for Renesas R-Car Video Input (VIN) driver. + Supports R-Car Gen2 SoCs. + + To compile this driver as a module, choose M here: the + module will be called rcar-vin. diff --git a/drivers/media/platform/rcar-vin/Makefile b/drivers/media/platform/rcar-vin/Makefile new file mode 100644 index 000000000000..48c5632c21dc --- /dev/null +++ b/drivers/media/platform/rcar-vin/Makefile @@ -0,0 +1,3 @@ +rcar-vin-objs = rcar-core.o rcar-dma.o rcar-v4l2.o + +obj-$(CONFIG_VIDEO_RCAR_VIN) += rcar-vin.o diff --git a/drivers/media/platform/rcar-vin/rcar-core.c b/drivers/media/platform/rcar-vin/rcar-core.c new file mode 100644 index 000000000000..4b2007b73463 --- /dev/null +++ b/drivers/media/platform/rcar-vin/rcar-core.c @@ -0,0 +1,334 @@ +/* + * Driver for Renesas R-Car VIN + * + * Copyright (C) 2016 Renesas Electronics Corp. + * Copyright (C) 2011-2013 Renesas Solutions Corp. + * Copyright (C) 2013 Cogent Embedded, Inc., <source@cogentembedded.com> + * Copyright (C) 2008 Magnus Damm + * + * Based on the soc-camera rcar_vin driver + * + * 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/of.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> + +#include <media/v4l2-of.h> + +#include "rcar-vin.h" + +/* ----------------------------------------------------------------------------- + * Async notifier + */ + +#define notifier_to_vin(n) container_of(n, struct rvin_dev, notifier) + +static int rvin_mbus_supported(struct rvin_dev *vin) +{ + struct v4l2_subdev *sd; + struct v4l2_subdev_mbus_code_enum code = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + + sd = vin_to_source(vin); + + code.index = 0; + while (!v4l2_subdev_call(sd, pad, enum_mbus_code, NULL, &code)) { + code.index++; + switch (code.code) { + case MEDIA_BUS_FMT_YUYV8_1X16: + case MEDIA_BUS_FMT_YUYV8_2X8: + case MEDIA_BUS_FMT_YUYV10_2X10: + case MEDIA_BUS_FMT_RGB888_1X24: + vin->source.code = code.code; + vin_dbg(vin, "Found supported media bus format: %d\n", + vin->source.code); + return true; + default: + break; + } + } + + return false; +} + +static int rvin_graph_notify_complete(struct v4l2_async_notifier *notifier) +{ + struct rvin_dev *vin = notifier_to_vin(notifier); + int ret; + + ret = v4l2_device_register_subdev_nodes(&vin->v4l2_dev); + if (ret < 0) { + vin_err(vin, "Failed to register subdev nodes\n"); + return ret; + } + + if (!rvin_mbus_supported(vin)) { + vin_err(vin, "No supported mediabus format found\n"); + return -EINVAL; + } + + return rvin_v4l2_probe(vin); +} + +static void rvin_graph_notify_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_subdev *asd) +{ + struct rvin_dev *vin = notifier_to_vin(notifier); + + rvin_v4l2_remove(vin); +} + +static int rvin_graph_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct rvin_dev *vin = notifier_to_vin(notifier); + + vin_dbg(vin, "subdev %s bound\n", subdev->name); + + vin->entity.entity = &subdev->entity; + vin->entity.subdev = subdev; + + return 0; +} + +static int rvin_graph_parse(struct rvin_dev *vin, + struct device_node *node) +{ + struct device_node *remote; + struct device_node *ep = NULL; + struct device_node *next; + int ret = 0; + + while (1) { + next = of_graph_get_next_endpoint(node, ep); + if (!next) + break; + + of_node_put(ep); + ep = next; + + remote = of_graph_get_remote_port_parent(ep); + if (!remote) { + ret = -EINVAL; + break; + } + + /* Skip entities that we have already processed. */ + if (remote == vin->dev->of_node) { + of_node_put(remote); + continue; + } + + /* Remote node to connect */ + if (!vin->entity.node) { + vin->entity.node = remote; + vin->entity.asd.match_type = V4L2_ASYNC_MATCH_OF; + vin->entity.asd.match.of.node = remote; + ret++; + } + } + + of_node_put(ep); + + return ret; +} + +static int rvin_graph_init(struct rvin_dev *vin) +{ + struct v4l2_async_subdev **subdevs = NULL; + int ret; + + /* Parse the graph to extract a list of subdevice DT nodes. */ + ret = rvin_graph_parse(vin, vin->dev->of_node); + if (ret < 0) { + vin_err(vin, "Graph parsing failed\n"); + goto done; + } + + if (!ret) { + vin_err(vin, "No subdev found in graph\n"); + goto done; + } + + if (ret != 1) { + vin_err(vin, "More then one subdev found in graph\n"); + goto done; + } + + /* Register the subdevices notifier. */ + subdevs = devm_kzalloc(vin->dev, sizeof(*subdevs), GFP_KERNEL); + if (subdevs == NULL) { + ret = -ENOMEM; + goto done; + } + + subdevs[0] = &vin->entity.asd; + + vin->notifier.subdevs = subdevs; + vin->notifier.num_subdevs = 1; + vin->notifier.bound = rvin_graph_notify_bound; + vin->notifier.unbind = rvin_graph_notify_unbind; + vin->notifier.complete = rvin_graph_notify_complete; + + ret = v4l2_async_notifier_register(&vin->v4l2_dev, &vin->notifier); + if (ret < 0) { + vin_err(vin, "Notifier registration failed\n"); + goto done; + } + + ret = 0; + +done: + if (ret < 0) { + v4l2_async_notifier_unregister(&vin->notifier); + of_node_put(vin->entity.node); + } + + return ret; +} + +/* ----------------------------------------------------------------------------- + * Platform Device Driver + */ + +static const struct of_device_id rvin_of_id_table[] = { + { .compatible = "renesas,vin-r8a7794", .data = (void *)RCAR_GEN2 }, + { .compatible = "renesas,vin-r8a7793", .data = (void *)RCAR_GEN2 }, + { .compatible = "renesas,vin-r8a7791", .data = (void *)RCAR_GEN2 }, + { .compatible = "renesas,vin-r8a7790", .data = (void *)RCAR_GEN2 }, + { .compatible = "renesas,vin-r8a7779", .data = (void *)RCAR_H1 }, + { .compatible = "renesas,vin-r8a7778", .data = (void *)RCAR_M1 }, + { }, +}; +MODULE_DEVICE_TABLE(of, rvin_of_id_table); + +static int rvin_parse_dt(struct rvin_dev *vin) +{ + const struct of_device_id *match; + struct v4l2_of_endpoint ep; + struct device_node *np; + int ret; + + match = of_match_device(of_match_ptr(rvin_of_id_table), vin->dev); + if (!match) + return -ENODEV; + + vin->chip = (enum chip_id)match->data; + + np = of_graph_get_next_endpoint(vin->dev->of_node, NULL); + if (!np) { + vin_err(vin, "Could not find endpoint\n"); + return -EINVAL; + } + + ret = v4l2_of_parse_endpoint(np, &ep); + if (ret) { + vin_err(vin, "Could not parse endpoint\n"); + return ret; + } + + of_node_put(np); + + vin->mbus_cfg.type = ep.bus_type; + + switch (vin->mbus_cfg.type) { + case V4L2_MBUS_PARALLEL: + vin->mbus_cfg.flags = ep.bus.parallel.flags; + break; + case V4L2_MBUS_BT656: + vin->mbus_cfg.flags = 0; + break; + default: + vin_err(vin, "Unknown media bus type\n"); + return -EINVAL; + } + + return 0; +} + +static int rcar_vin_probe(struct platform_device *pdev) +{ + struct rvin_dev *vin; + struct resource *mem; + int irq, ret; + + vin = devm_kzalloc(&pdev->dev, sizeof(*vin), GFP_KERNEL); + if (!vin) + return -ENOMEM; + + vin->dev = &pdev->dev; + + ret = rvin_parse_dt(vin); + if (ret) + return ret; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (mem == NULL) + return -EINVAL; + + vin->base = devm_ioremap_resource(vin->dev, mem); + if (IS_ERR(vin->base)) + return PTR_ERR(vin->base); + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) + return ret; + + ret = rvin_dma_probe(vin, irq); + if (ret) + return ret; + + ret = rvin_graph_init(vin); + if (ret < 0) + goto error; + + pm_suspend_ignore_children(&pdev->dev, true); + pm_runtime_enable(&pdev->dev); + + platform_set_drvdata(pdev, vin); + + return 0; +error: + rvin_dma_remove(vin); + + return ret; +} + +static int rcar_vin_remove(struct platform_device *pdev) +{ + struct rvin_dev *vin = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + + v4l2_async_notifier_unregister(&vin->notifier); + + rvin_dma_remove(vin); + + return 0; +} + +static struct platform_driver rcar_vin_driver = { + .driver = { + .name = "rcar-vin", + .of_match_table = rvin_of_id_table, + }, + .probe = rcar_vin_probe, + .remove = rcar_vin_remove, +}; + +module_platform_driver(rcar_vin_driver); + +MODULE_AUTHOR("Niklas Söderlund <niklas.soderlund@ragnatech.se>"); +MODULE_DESCRIPTION("Renesas R-Car VIN camera host driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/rcar-vin/rcar-dma.c b/drivers/media/platform/rcar-vin/rcar-dma.c new file mode 100644 index 000000000000..dad3b031f778 --- /dev/null +++ b/drivers/media/platform/rcar-vin/rcar-dma.c @@ -0,0 +1,1196 @@ +/* + * Driver for Renesas R-Car VIN + * + * Copyright (C) 2016 Renesas Electronics Corp. + * Copyright (C) 2011-2013 Renesas Solutions Corp. + * Copyright (C) 2013 Cogent Embedded, Inc., <source@cogentembedded.com> + * Copyright (C) 2008 Magnus Damm + * + * Based on the soc-camera rcar_vin driver + * + * 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 <media/videobuf2-dma-contig.h> + +#include "rcar-vin.h" + +/* ----------------------------------------------------------------------------- + * HW Functions + */ + +/* Register offsets for R-Car VIN */ +#define VNMC_REG 0x00 /* Video n Main Control Register */ +#define VNMS_REG 0x04 /* Video n Module Status Register */ +#define VNFC_REG 0x08 /* Video n Frame Capture Register */ +#define VNSLPRC_REG 0x0C /* Video n Start Line Pre-Clip Register */ +#define VNELPRC_REG 0x10 /* Video n End Line Pre-Clip Register */ +#define VNSPPRC_REG 0x14 /* Video n Start Pixel Pre-Clip Register */ +#define VNEPPRC_REG 0x18 /* Video n End Pixel Pre-Clip Register */ +#define VNSLPOC_REG 0x1C /* Video n Start Line Post-Clip Register */ +#define VNELPOC_REG 0x20 /* Video n End Line Post-Clip Register */ +#define VNSPPOC_REG 0x24 /* Video n Start Pixel Post-Clip Register */ +#define VNEPPOC_REG 0x28 /* Video n End Pixel Post-Clip Register */ +#define VNIS_REG 0x2C /* Video n Image Stride Register */ +#define VNMB_REG(m) (0x30 + ((m) << 2)) /* Video n Memory Base m Register */ +#define VNIE_REG 0x40 /* Video n Interrupt Enable Register */ +#define VNINTS_REG 0x44 /* Video n Interrupt Status Register */ +#define VNSI_REG 0x48 /* Video n Scanline Interrupt Register */ +#define VNMTC_REG 0x4C /* Video n Memory Transfer Control Register */ +#define VNYS_REG 0x50 /* Video n Y Scale Register */ +#define VNXS_REG 0x54 /* Video n X Scale Register */ +#define VNDMR_REG 0x58 /* Video n Data Mode Register */ +#define VNDMR2_REG 0x5C /* Video n Data Mode Register 2 */ +#define VNUVAOF_REG 0x60 /* Video n UV Address Offset Register */ +#define VNC1A_REG 0x80 /* Video n Coefficient Set C1A Register */ +#define VNC1B_REG 0x84 /* Video n Coefficient Set C1B Register */ +#define VNC1C_REG 0x88 /* Video n Coefficient Set C1C Register */ +#define VNC2A_REG 0x90 /* Video n Coefficient Set C2A Register */ +#define VNC2B_REG 0x94 /* Video n Coefficient Set C2B Register */ +#define VNC2C_REG 0x98 /* Video n Coefficient Set C2C Register */ +#define VNC3A_REG 0xA0 /* Video n Coefficient Set C3A Register */ +#define VNC3B_REG 0xA4 /* Video n Coefficient Set C3B Register */ +#define VNC3C_REG 0xA8 /* Video n Coefficient Set C3C Register */ +#define VNC4A_REG 0xB0 /* Video n Coefficient Set C4A Register */ +#define VNC4B_REG 0xB4 /* Video n Coefficient Set C4B Register */ +#define VNC4C_REG 0xB8 /* Video n Coefficient Set C4C Register */ +#define VNC5A_REG 0xC0 /* Video n Coefficient Set C5A Register */ +#define VNC5B_REG 0xC4 /* Video n Coefficient Set C5B Register */ +#define VNC5C_REG 0xC8 /* Video n Coefficient Set C5C Register */ +#define VNC6A_REG 0xD0 /* Video n Coefficient Set C6A Register */ +#define VNC6B_REG 0xD4 /* Video n Coefficient Set C6B Register */ +#define VNC6C_REG 0xD8 /* Video n Coefficient Set C6C Register */ +#define VNC7A_REG 0xE0 /* Video n Coefficient Set C7A Register */ +#define VNC7B_REG 0xE4 /* Video n Coefficient Set C7B Register */ +#define VNC7C_REG 0xE8 /* Video n Coefficient Set C7C Register */ +#define VNC8A_REG 0xF0 /* Video n Coefficient Set C8A Register */ +#define VNC8B_REG 0xF4 /* Video n Coefficient Set C8B Register */ +#define VNC8C_REG 0xF8 /* Video n Coefficient Set C8C Register */ + + +/* Register bit fields for R-Car VIN */ +/* Video n Main Control Register bits */ +#define VNMC_FOC (1 << 21) +#define VNMC_YCAL (1 << 19) +#define VNMC_INF_YUV8_BT656 (0 << 16) +#define VNMC_INF_YUV8_BT601 (1 << 16) +#define VNMC_INF_YUV10_BT656 (2 << 16) +#define VNMC_INF_YUV10_BT601 (3 << 16) +#define VNMC_INF_YUV16 (5 << 16) +#define VNMC_INF_RGB888 (6 << 16) +#define VNMC_VUP (1 << 10) +#define VNMC_IM_ODD (0 << 3) +#define VNMC_IM_ODD_EVEN (1 << 3) +#define VNMC_IM_EVEN (2 << 3) +#define VNMC_IM_FULL (3 << 3) +#define VNMC_BPS (1 << 1) +#define VNMC_ME (1 << 0) + +/* Video n Module Status Register bits */ +#define VNMS_FBS_MASK (3 << 3) +#define VNMS_FBS_SHIFT 3 +#define VNMS_AV (1 << 1) +#define VNMS_CA (1 << 0) + +/* Video n Frame Capture Register bits */ +#define VNFC_C_FRAME (1 << 1) +#define VNFC_S_FRAME (1 << 0) + +/* Video n Interrupt Enable Register bits */ +#define VNIE_FIE (1 << 4) +#define VNIE_EFE (1 << 1) + +/* Video n Data Mode Register bits */ +#define VNDMR_EXRGB (1 << 8) +#define VNDMR_BPSM (1 << 4) +#define VNDMR_DTMD_YCSEP (1 << 1) +#define VNDMR_DTMD_ARGB1555 (1 << 0) + +/* Video n Data Mode Register 2 bits */ +#define VNDMR2_VPS (1 << 30) +#define VNDMR2_HPS (1 << 29) +#define VNDMR2_FTEV (1 << 17) +#define VNDMR2_VLV(n) ((n & 0xf) << 12) + +static void rvin_write(struct rvin_dev *vin, u32 value, u32 offset) +{ + iowrite32(value, vin->base + offset); +} + +static u32 rvin_read(struct rvin_dev *vin, u32 offset) +{ + return ioread32(vin->base + offset); +} + +static int rvin_setup(struct rvin_dev *vin) +{ + u32 vnmc, dmr, dmr2, interrupts; + bool progressive = false, output_is_yuv = false, input_is_yuv = false; + + switch (vin->format.field) { + case V4L2_FIELD_TOP: + vnmc = VNMC_IM_ODD; + break; + case V4L2_FIELD_BOTTOM: + vnmc = VNMC_IM_EVEN; + break; + case V4L2_FIELD_INTERLACED: + case V4L2_FIELD_INTERLACED_TB: + vnmc = VNMC_IM_FULL; + break; + case V4L2_FIELD_INTERLACED_BT: + vnmc = VNMC_IM_FULL | VNMC_FOC; + break; + case V4L2_FIELD_NONE: + if (vin->continuous) { + vnmc = VNMC_IM_ODD_EVEN; + progressive = true; + } else { + vnmc = VNMC_IM_ODD; + } + break; + default: + vnmc = VNMC_IM_ODD; + break; + } + + /* + * Input interface + */ + switch (vin->source.code) { + case MEDIA_BUS_FMT_YUYV8_1X16: + /* BT.601/BT.1358 16bit YCbCr422 */ + vnmc |= VNMC_INF_YUV16; + input_is_yuv = true; + break; + case MEDIA_BUS_FMT_YUYV8_2X8: + /* BT.656 8bit YCbCr422 or BT.601 8bit YCbCr422 */ + vnmc |= vin->mbus_cfg.type == V4L2_MBUS_BT656 ? + VNMC_INF_YUV8_BT656 : VNMC_INF_YUV8_BT601; + input_is_yuv = true; + break; + case MEDIA_BUS_FMT_RGB888_1X24: + vnmc |= VNMC_INF_RGB888; + break; + case MEDIA_BUS_FMT_YUYV10_2X10: + /* BT.656 10bit YCbCr422 or BT.601 10bit YCbCr422 */ + vnmc |= vin->mbus_cfg.type == V4L2_MBUS_BT656 ? + VNMC_INF_YUV10_BT656 : VNMC_INF_YUV10_BT601; + input_is_yuv = true; + break; + default: + break; + } + + /* Enable VSYNC Field Toogle mode after one VSYNC input */ + dmr2 = VNDMR2_FTEV | VNDMR2_VLV(1); + + /* Hsync Signal Polarity Select */ + if (!(vin->mbus_cfg.flags & V4L2_MBUS_HSYNC_ACTIVE_LOW)) + dmr2 |= VNDMR2_HPS; + + /* Vsync Signal Polarity Select */ + if (!(vin->mbus_cfg.flags & V4L2_MBUS_VSYNC_ACTIVE_LOW)) + dmr2 |= VNDMR2_VPS; + + /* + * Output format + */ + switch (vin->format.pixelformat) { + case V4L2_PIX_FMT_NV16: + rvin_write(vin, + ALIGN(vin->format.width * vin->format.height, 0x80), + VNUVAOF_REG); + dmr = VNDMR_DTMD_YCSEP; + output_is_yuv = true; + break; + case V4L2_PIX_FMT_YUYV: + dmr = VNDMR_BPSM; + output_is_yuv = true; + break; + case V4L2_PIX_FMT_UYVY: + dmr = 0; + output_is_yuv = true; + break; + case V4L2_PIX_FMT_XRGB555: + dmr = VNDMR_DTMD_ARGB1555; + break; + case V4L2_PIX_FMT_RGB565: + dmr = 0; + break; + case V4L2_PIX_FMT_XBGR32: + if (vin->chip == RCAR_GEN2 || vin->chip == RCAR_H1) { + dmr = VNDMR_EXRGB; + break; + } + /* fall through */ + default: + vin_err(vin, "Invalid pixelformat (0x%x)\n", + vin->format.pixelformat); + return -EINVAL; + } + + /* Always update on field change */ + vnmc |= VNMC_VUP; + + /* If input and output use the same colorspace, use bypass mode */ + if (input_is_yuv == output_is_yuv) + vnmc |= VNMC_BPS; + + /* Progressive or interlaced mode */ + interrupts = progressive ? VNIE_FIE : VNIE_EFE; + + /* Ack interrupts */ + rvin_write(vin, interrupts, VNINTS_REG); + /* Enable interrupts */ + rvin_write(vin, interrupts, VNIE_REG); + /* Start capturing */ + rvin_write(vin, dmr, VNDMR_REG); + rvin_write(vin, dmr2, VNDMR2_REG); + + /* Enable module */ + rvin_write(vin, vnmc | VNMC_ME, VNMC_REG); + + 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); +} + +static u32 rvin_get_interrupt_status(struct rvin_dev *vin) +{ + return rvin_read(vin, VNINTS_REG); +} + +static void rvin_ack_interrupt(struct rvin_dev *vin) +{ + rvin_write(vin, rvin_read(vin, VNINTS_REG), VNINTS_REG); +} + +static bool rvin_capture_active(struct rvin_dev *vin) +{ + return rvin_read(vin, VNMS_REG) & VNMS_CA; +} + +static int rvin_get_active_slot(struct rvin_dev *vin) +{ + if (vin->continuous) + return (rvin_read(vin, VNMS_REG) & VNMS_FBS_MASK) + >> VNMS_FBS_SHIFT; + + return 0; +} + +static void rvin_set_slot_addr(struct rvin_dev *vin, int slot, dma_addr_t addr) +{ + const struct rvin_video_format *fmt; + int offsetx, offsety; + dma_addr_t offset; + + fmt = rvin_format_from_pixel(vin->format.pixelformat); + + /* + * There is no HW support for composition do the beast we can + * by modifying the buffer offset + */ + offsetx = vin->compose.left * fmt->bpp; + offsety = vin->compose.top * vin->format.bytesperline; + offset = addr + offsetx + offsety; + + /* + * The address needs to be 128 bytes aligned. Driver should never accept + * settings that do not satisfy this in the first place... + */ + if (WARN_ON((offsetx | offsety | offset) & HW_BUFFER_MASK)) + return; + + rvin_write(vin, offset, VNMB_REG(slot)); +} + +/* ----------------------------------------------------------------------------- + * Crop and Scaling Gen2 + */ + +struct vin_coeff { + unsigned short xs_value; + u32 coeff_set[24]; +}; + +static const struct vin_coeff vin_coeff_set[] = { + { 0x0000, { + 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000 }, + }, + { 0x1000, { + 0x000fa400, 0x000fa400, 0x09625902, + 0x000003f8, 0x00000403, 0x3de0d9f0, + 0x001fffed, 0x00000804, 0x3cc1f9c3, + 0x001003de, 0x00000c01, 0x3cb34d7f, + 0x002003d2, 0x00000c00, 0x3d24a92d, + 0x00200bca, 0x00000bff, 0x3df600d2, + 0x002013cc, 0x000007ff, 0x3ed70c7e, + 0x00100fde, 0x00000000, 0x3f87c036 }, + }, + { 0x1200, { + 0x002ffff1, 0x002ffff1, 0x02a0a9c8, + 0x002003e7, 0x001ffffa, 0x000185bc, + 0x002007dc, 0x000003ff, 0x3e52859c, + 0x00200bd4, 0x00000002, 0x3d53996b, + 0x00100fd0, 0x00000403, 0x3d04ad2d, + 0x00000bd5, 0x00000403, 0x3d35ace7, + 0x3ff003e4, 0x00000801, 0x3dc674a1, + 0x3fffe800, 0x00000800, 0x3e76f461 }, + }, + { 0x1400, { + 0x00100be3, 0x00100be3, 0x04d1359a, + 0x00000fdb, 0x002003ed, 0x0211fd93, + 0x00000fd6, 0x002003f4, 0x0002d97b, + 0x000007d6, 0x002ffffb, 0x3e93b956, + 0x3ff003da, 0x001003ff, 0x3db49926, + 0x3fffefe9, 0x00100001, 0x3d655cee, + 0x3fffd400, 0x00000003, 0x3d65f4b6, + 0x000fb421, 0x00000402, 0x3dc6547e }, + }, + { 0x1600, { + 0x00000bdd, 0x00000bdd, 0x06519578, + 0x3ff007da, 0x00000be3, 0x03c24973, + 0x3ff003d9, 0x00000be9, 0x01b30d5f, + 0x3ffff7df, 0x001003f1, 0x0003c542, + 0x000fdfec, 0x001003f7, 0x3ec4711d, + 0x000fc400, 0x002ffffd, 0x3df504f1, + 0x001fa81a, 0x002ffc00, 0x3d957cc2, + 0x002f8c3c, 0x00100000, 0x3db5c891 }, + }, + { 0x1800, { + 0x3ff003dc, 0x3ff003dc, 0x0791e558, + 0x000ff7dd, 0x3ff007de, 0x05328554, + 0x000fe7e3, 0x3ff00be2, 0x03232546, + 0x000fd7ee, 0x000007e9, 0x0143bd30, + 0x001fb800, 0x000007ee, 0x00044511, + 0x002fa015, 0x000007f4, 0x3ef4bcee, + 0x002f8832, 0x001003f9, 0x3e4514c7, + 0x001f7853, 0x001003fd, 0x3de54c9f }, + }, + { 0x1a00, { + 0x000fefe0, 0x000fefe0, 0x08721d3c, + 0x001fdbe7, 0x000ffbde, 0x0652a139, + 0x001fcbf0, 0x000003df, 0x0463292e, + 0x002fb3ff, 0x3ff007e3, 0x0293a91d, + 0x002f9c12, 0x3ff00be7, 0x01241905, + 0x001f8c29, 0x000007ed, 0x3fe470eb, + 0x000f7c46, 0x000007f2, 0x3f04b8ca, + 0x3fef7865, 0x000007f6, 0x3e74e4a8 }, + }, + { 0x1c00, { + 0x001fd3e9, 0x001fd3e9, 0x08f23d26, + 0x002fbff3, 0x001fe3e4, 0x0712ad23, + 0x002fa800, 0x000ff3e0, 0x05631d1b, + 0x001f9810, 0x000ffbe1, 0x03b3890d, + 0x000f8c23, 0x000003e3, 0x0233e8fa, + 0x3fef843b, 0x000003e7, 0x00f430e4, + 0x3fbf8456, 0x3ff00bea, 0x00046cc8, + 0x3f8f8c72, 0x3ff00bef, 0x3f3490ac }, + }, + { 0x1e00, { + 0x001fbbf4, 0x001fbbf4, 0x09425112, + 0x001fa800, 0x002fc7ed, 0x0792b110, + 0x000f980e, 0x001fdbe6, 0x0613110a, + 0x3fff8c20, 0x001fe7e3, 0x04a368fd, + 0x3fcf8c33, 0x000ff7e2, 0x0343b8ed, + 0x3f9f8c4a, 0x000fffe3, 0x0203f8da, + 0x3f5f9c61, 0x000003e6, 0x00e428c5, + 0x3f1fb07b, 0x000003eb, 0x3fe440af }, + }, + { 0x2000, { + 0x000fa400, 0x000fa400, 0x09625902, + 0x3fff980c, 0x001fb7f5, 0x0812b0ff, + 0x3fdf901c, 0x001fc7ed, 0x06b2fcfa, + 0x3faf902d, 0x001fd3e8, 0x055348f1, + 0x3f7f983f, 0x001fe3e5, 0x04038ce3, + 0x3f3fa454, 0x001fefe3, 0x02e3c8d1, + 0x3f0fb86a, 0x001ff7e4, 0x01c3e8c0, + 0x3ecfd880, 0x000fffe6, 0x00c404ac }, + }, + { 0x2200, { + 0x3fdf9c0b, 0x3fdf9c0b, 0x09725cf4, + 0x3fbf9818, 0x3fffa400, 0x0842a8f1, + 0x3f8f9827, 0x000fb3f7, 0x0702f0ec, + 0x3f5fa037, 0x000fc3ef, 0x05d330e4, + 0x3f2fac49, 0x001fcfea, 0x04a364d9, + 0x3effc05c, 0x001fdbe7, 0x038394ca, + 0x3ecfdc6f, 0x001fe7e6, 0x0273b0bb, + 0x3ea00083, 0x001fefe6, 0x0183c0a9 }, + }, + { 0x2400, { + 0x3f9fa014, 0x3f9fa014, 0x098260e6, + 0x3f7f9c23, 0x3fcf9c0a, 0x08629ce5, + 0x3f4fa431, 0x3fefa400, 0x0742d8e1, + 0x3f1fb440, 0x3fffb3f8, 0x062310d9, + 0x3eefc850, 0x000fbbf2, 0x050340d0, + 0x3ecfe062, 0x000fcbec, 0x041364c2, + 0x3ea00073, 0x001fd3ea, 0x03037cb5, + 0x3e902086, 0x001fdfe8, 0x022388a5 }, + }, + { 0x2600, { + 0x3f5fa81e, 0x3f5fa81e, 0x096258da, + 0x3f3fac2b, 0x3f8fa412, 0x088290d8, + 0x3f0fbc38, 0x3fafa408, 0x0772c8d5, + 0x3eefcc47, 0x3fcfa800, 0x0672f4ce, + 0x3ecfe456, 0x3fefaffa, 0x05531cc6, + 0x3eb00066, 0x3fffbbf3, 0x047334bb, + 0x3ea01c77, 0x000fc7ee, 0x039348ae, + 0x3ea04486, 0x000fd3eb, 0x02b350a1 }, + }, + { 0x2800, { + 0x3f2fb426, 0x3f2fb426, 0x094250ce, + 0x3f0fc032, 0x3f4fac1b, 0x086284cd, + 0x3eefd040, 0x3f7fa811, 0x0782acc9, + 0x3ecfe84c, 0x3f9fa807, 0x06a2d8c4, + 0x3eb0005b, 0x3fbfac00, 0x05b2f4bc, + 0x3eb0186a, 0x3fdfb3fa, 0x04c308b4, + 0x3eb04077, 0x3fefbbf4, 0x03f31ca8, + 0x3ec06884, 0x000fbff2, 0x03031c9e }, + }, + { 0x2a00, { + 0x3f0fc42d, 0x3f0fc42d, 0x090240c4, + 0x3eefd439, 0x3f2fb822, 0x08526cc2, + 0x3edfe845, 0x3f4fb018, 0x078294bf, + 0x3ec00051, 0x3f6fac0f, 0x06b2b4bb, + 0x3ec0185f, 0x3f8fac07, 0x05e2ccb4, + 0x3ec0386b, 0x3fafac00, 0x0502e8ac, + 0x3ed05c77, 0x3fcfb3fb, 0x0432f0a3, + 0x3ef08482, 0x3fdfbbf6, 0x0372f898 }, + }, + { 0x2c00, { + 0x3eefdc31, 0x3eefdc31, 0x08e238b8, + 0x3edfec3d, 0x3f0fc828, 0x082258b9, + 0x3ed00049, 0x3f1fc01e, 0x077278b6, + 0x3ed01455, 0x3f3fb815, 0x06c294b2, + 0x3ed03460, 0x3f5fb40d, 0x0602acac, + 0x3ef0506c, 0x3f7fb006, 0x0542c0a4, + 0x3f107476, 0x3f9fb400, 0x0472c89d, + 0x3f309c80, 0x3fbfb7fc, 0x03b2cc94 }, + }, + { 0x2e00, { + 0x3eefec37, 0x3eefec37, 0x088220b0, + 0x3ee00041, 0x3effdc2d, 0x07f244ae, + 0x3ee0144c, 0x3f0fd023, 0x07625cad, + 0x3ef02c57, 0x3f1fc81a, 0x06c274a9, + 0x3f004861, 0x3f3fbc13, 0x060288a6, + 0x3f20686b, 0x3f5fb80c, 0x05529c9e, + 0x3f408c74, 0x3f6fb805, 0x04b2ac96, + 0x3f80ac7e, 0x3f8fb800, 0x0402ac8e }, + }, + { 0x3000, { + 0x3ef0003a, 0x3ef0003a, 0x084210a6, + 0x3ef01045, 0x3effec32, 0x07b228a7, + 0x3f00284e, 0x3f0fdc29, 0x073244a4, + 0x3f104058, 0x3f0fd420, 0x06a258a2, + 0x3f305c62, 0x3f2fc818, 0x0612689d, + 0x3f508069, 0x3f3fc011, 0x05728496, + 0x3f80a072, 0x3f4fc00a, 0x04d28c90, + 0x3fc0c07b, 0x3f6fbc04, 0x04429088 }, + }, + { 0x3200, { + 0x3f00103e, 0x3f00103e, 0x07f1fc9e, + 0x3f102447, 0x3f000035, 0x0782149d, + 0x3f203c4f, 0x3f0ff02c, 0x07122c9c, + 0x3f405458, 0x3f0fe424, 0x06924099, + 0x3f607061, 0x3f1fd41d, 0x06024c97, + 0x3f909068, 0x3f2fcc16, 0x05726490, + 0x3fc0b070, 0x3f3fc80f, 0x04f26c8a, + 0x0000d077, 0x3f4fc409, 0x04627484 }, + }, + { 0x3400, { + 0x3f202040, 0x3f202040, 0x07a1e898, + 0x3f303449, 0x3f100c38, 0x0741fc98, + 0x3f504c50, 0x3f10002f, 0x06e21495, + 0x3f706459, 0x3f1ff028, 0x06722492, + 0x3fa08060, 0x3f1fe421, 0x05f2348f, + 0x3fd09c67, 0x3f1fdc19, 0x05824c89, + 0x0000bc6e, 0x3f2fd014, 0x04f25086, + 0x0040dc74, 0x3f3fcc0d, 0x04825c7f }, + }, + { 0x3600, { + 0x3f403042, 0x3f403042, 0x0761d890, + 0x3f504848, 0x3f301c3b, 0x0701f090, + 0x3f805c50, 0x3f200c33, 0x06a2008f, + 0x3fa07458, 0x3f10002b, 0x06520c8d, + 0x3fd0905e, 0x3f1ff424, 0x05e22089, + 0x0000ac65, 0x3f1fe81d, 0x05823483, + 0x0030cc6a, 0x3f2fdc18, 0x04f23c81, + 0x0080e871, 0x3f2fd412, 0x0482407c }, + }, + { 0x3800, { + 0x3f604043, 0x3f604043, 0x0721c88a, + 0x3f80544a, 0x3f502c3c, 0x06d1d88a, + 0x3fb06851, 0x3f301c35, 0x0681e889, + 0x3fd08456, 0x3f30082f, 0x0611fc88, + 0x00009c5d, 0x3f200027, 0x05d20884, + 0x0030b863, 0x3f2ff421, 0x05621880, + 0x0070d468, 0x3f2fe81b, 0x0502247c, + 0x00c0ec6f, 0x3f2fe015, 0x04a22877 }, + }, + { 0x3a00, { + 0x3f904c44, 0x3f904c44, 0x06e1b884, + 0x3fb0604a, 0x3f70383e, 0x0691c885, + 0x3fe07451, 0x3f502c36, 0x0661d483, + 0x00009055, 0x3f401831, 0x0601ec81, + 0x0030a85b, 0x3f300c2a, 0x05b1f480, + 0x0070c061, 0x3f300024, 0x0562047a, + 0x00b0d867, 0x3f3ff41e, 0x05020c77, + 0x00f0f46b, 0x3f2fec19, 0x04a21474 }, + }, + { 0x3c00, { + 0x3fb05c43, 0x3fb05c43, 0x06c1b07e, + 0x3fe06c4b, 0x3f902c3f, 0x0681c081, + 0x0000844f, 0x3f703838, 0x0631cc7d, + 0x00309855, 0x3f602433, 0x05d1d47e, + 0x0060b459, 0x3f50142e, 0x0581e47b, + 0x00a0c85f, 0x3f400828, 0x0531f078, + 0x00e0e064, 0x3f300021, 0x0501fc73, + 0x00b0fc6a, 0x3f3ff41d, 0x04a20873 }, + }, + { 0x3e00, { + 0x3fe06444, 0x3fe06444, 0x0681a07a, + 0x00007849, 0x3fc0503f, 0x0641b07a, + 0x0020904d, 0x3fa0403a, 0x05f1c07a, + 0x0060a453, 0x3f803034, 0x05c1c878, + 0x0090b858, 0x3f70202f, 0x0571d477, + 0x00d0d05d, 0x3f501829, 0x0531e073, + 0x0110e462, 0x3f500825, 0x04e1e471, + 0x01510065, 0x3f40001f, 0x04a1f06d }, + }, + { 0x4000, { + 0x00007044, 0x00007044, 0x06519476, + 0x00208448, 0x3fe05c3f, 0x0621a476, + 0x0050984d, 0x3fc04c3a, 0x05e1b075, + 0x0080ac52, 0x3fa03c35, 0x05a1b875, + 0x00c0c056, 0x3f803030, 0x0561c473, + 0x0100d45b, 0x3f70202b, 0x0521d46f, + 0x0140e860, 0x3f601427, 0x04d1d46e, + 0x01810064, 0x3f500822, 0x0491dc6b }, + }, + { 0x5000, { + 0x0110a442, 0x0110a442, 0x0551545e, + 0x0140b045, 0x00e0983f, 0x0531585f, + 0x0160c047, 0x00c08c3c, 0x0511645e, + 0x0190cc4a, 0x00908039, 0x04f1685f, + 0x01c0dc4c, 0x00707436, 0x04d1705e, + 0x0200e850, 0x00506833, 0x04b1785b, + 0x0230f453, 0x00305c30, 0x0491805a, + 0x02710056, 0x0010542d, 0x04718059 }, + }, + { 0x6000, { + 0x01c0bc40, 0x01c0bc40, 0x04c13052, + 0x01e0c841, 0x01a0b43d, 0x04c13851, + 0x0210cc44, 0x0180a83c, 0x04a13453, + 0x0230d845, 0x0160a03a, 0x04913c52, + 0x0260e047, 0x01409838, 0x04714052, + 0x0280ec49, 0x01208c37, 0x04514c50, + 0x02b0f44b, 0x01008435, 0x04414c50, + 0x02d1004c, 0x00e07c33, 0x0431544f }, + }, + { 0x7000, { + 0x0230c83e, 0x0230c83e, 0x04711c4c, + 0x0250d03f, 0x0210c43c, 0x0471204b, + 0x0270d840, 0x0200b83c, 0x0451244b, + 0x0290dc42, 0x01e0b43a, 0x0441244c, + 0x02b0e443, 0x01c0b038, 0x0441284b, + 0x02d0ec44, 0x01b0a438, 0x0421304a, + 0x02f0f445, 0x0190a036, 0x04213449, + 0x0310f847, 0x01709c34, 0x04213848 }, + }, + { 0x8000, { + 0x0280d03d, 0x0280d03d, 0x04310c48, + 0x02a0d43e, 0x0270c83c, 0x04311047, + 0x02b0dc3e, 0x0250c83a, 0x04311447, + 0x02d0e040, 0x0240c03a, 0x04211446, + 0x02e0e840, 0x0220bc39, 0x04111847, + 0x0300e842, 0x0210b438, 0x04012445, + 0x0310f043, 0x0200b037, 0x04012045, + 0x0330f444, 0x01e0ac36, 0x03f12445 }, + }, + { 0xefff, { + 0x0340dc3a, 0x0340dc3a, 0x03b0ec40, + 0x0340e03a, 0x0330e039, 0x03c0f03e, + 0x0350e03b, 0x0330dc39, 0x03c0ec3e, + 0x0350e43a, 0x0320dc38, 0x03c0f43e, + 0x0360e43b, 0x0320d839, 0x03b0f03e, + 0x0360e83b, 0x0310d838, 0x03c0fc3b, + 0x0370e83b, 0x0310d439, 0x03a0f83d, + 0x0370e83c, 0x0300d438, 0x03b0fc3c }, + } +}; + +static void rvin_set_coeff(struct rvin_dev *vin, unsigned short xs) +{ + int i; + const struct vin_coeff *p_prev_set = NULL; + const struct vin_coeff *p_set = NULL; + + /* Look for suitable coefficient values */ + for (i = 0; i < ARRAY_SIZE(vin_coeff_set); i++) { + p_prev_set = p_set; + p_set = &vin_coeff_set[i]; + + if (xs < p_set->xs_value) + break; + } + + /* Use previous value if its XS value is closer */ + if (p_prev_set && p_set && + xs - p_prev_set->xs_value < p_set->xs_value - xs) + p_set = p_prev_set; + + /* Set coefficient registers */ + rvin_write(vin, p_set->coeff_set[0], VNC1A_REG); + rvin_write(vin, p_set->coeff_set[1], VNC1B_REG); + rvin_write(vin, p_set->coeff_set[2], VNC1C_REG); + + rvin_write(vin, p_set->coeff_set[3], VNC2A_REG); + rvin_write(vin, p_set->coeff_set[4], VNC2B_REG); + rvin_write(vin, p_set->coeff_set[5], VNC2C_REG); + + rvin_write(vin, p_set->coeff_set[6], VNC3A_REG); + rvin_write(vin, p_set->coeff_set[7], VNC3B_REG); + rvin_write(vin, p_set->coeff_set[8], VNC3C_REG); + + rvin_write(vin, p_set->coeff_set[9], VNC4A_REG); + rvin_write(vin, p_set->coeff_set[10], VNC4B_REG); + rvin_write(vin, p_set->coeff_set[11], VNC4C_REG); + + rvin_write(vin, p_set->coeff_set[12], VNC5A_REG); + rvin_write(vin, p_set->coeff_set[13], VNC5B_REG); + rvin_write(vin, p_set->coeff_set[14], VNC5C_REG); + + rvin_write(vin, p_set->coeff_set[15], VNC6A_REG); + rvin_write(vin, p_set->coeff_set[16], VNC6B_REG); + rvin_write(vin, p_set->coeff_set[17], VNC6C_REG); + + rvin_write(vin, p_set->coeff_set[18], VNC7A_REG); + rvin_write(vin, p_set->coeff_set[19], VNC7B_REG); + rvin_write(vin, p_set->coeff_set[20], VNC7C_REG); + + rvin_write(vin, p_set->coeff_set[21], VNC8A_REG); + rvin_write(vin, p_set->coeff_set[22], VNC8B_REG); + rvin_write(vin, p_set->coeff_set[23], VNC8C_REG); +} + +void rvin_crop_scale_comp(struct rvin_dev *vin) +{ + u32 xs, ys; + + /* Set Start/End Pixel/Line Pre-Clip */ + rvin_write(vin, vin->crop.left, VNSPPRC_REG); + rvin_write(vin, vin->crop.left + vin->crop.width - 1, VNEPPRC_REG); + switch (vin->format.field) { + case V4L2_FIELD_INTERLACED: + case V4L2_FIELD_INTERLACED_TB: + case V4L2_FIELD_INTERLACED_BT: + rvin_write(vin, vin->crop.top / 2, VNSLPRC_REG); + rvin_write(vin, (vin->crop.top + vin->crop.height) / 2 - 1, + VNELPRC_REG); + break; + default: + rvin_write(vin, vin->crop.top, VNSLPRC_REG); + rvin_write(vin, vin->crop.top + vin->crop.height - 1, + VNELPRC_REG); + break; + } + + /* Set scaling coefficient */ + ys = 0; + if (vin->crop.height != vin->compose.height) + ys = (4096 * vin->crop.height) / vin->compose.height; + rvin_write(vin, ys, VNYS_REG); + + xs = 0; + if (vin->crop.width != vin->compose.width) + xs = (4096 * vin->crop.width) / vin->compose.width; + + /* Horizontal upscaling is up to double size */ + if (xs > 0 && xs < 2048) + xs = 2048; + + rvin_write(vin, xs, VNXS_REG); + + /* Horizontal upscaling is done out by scaling down from double size */ + if (xs < 4096) + xs *= 2; + + rvin_set_coeff(vin, xs); + + /* Set Start/End Pixel/Line Post-Clip */ + rvin_write(vin, 0, VNSPPOC_REG); + rvin_write(vin, 0, VNSLPOC_REG); + rvin_write(vin, vin->format.width - 1, VNEPPOC_REG); + switch (vin->format.field) { + case V4L2_FIELD_INTERLACED: + case V4L2_FIELD_INTERLACED_TB: + case V4L2_FIELD_INTERLACED_BT: + rvin_write(vin, vin->format.height / 2 - 1, VNELPOC_REG); + break; + default: + rvin_write(vin, vin->format.height - 1, VNELPOC_REG); + break; + } + + if (vin->format.pixelformat == V4L2_PIX_FMT_NV16) + rvin_write(vin, ALIGN(vin->format.width, 0x20), VNIS_REG); + else + rvin_write(vin, ALIGN(vin->format.width, 0x10), VNIS_REG); + + vin_dbg(vin, + "Pre-Clip: %ux%u@%u:%u YS: %d XS: %d Post-Clip: %ux%u@%u:%u\n", + vin->crop.width, vin->crop.height, vin->crop.left, + vin->crop.top, ys, xs, vin->format.width, vin->format.height, + 0, 0); +} + +void rvin_scale_try(struct rvin_dev *vin, struct v4l2_pix_format *pix, + u32 width, u32 height) +{ + /* All VIN channels on Gen2 have scalers */ + pix->width = width; + pix->height = height; +} + +/* ----------------------------------------------------------------------------- + * DMA Functions + */ + +#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; + int slot; + unsigned int sequence, handled = 0; + unsigned long flags; + + spin_lock_irqsave(&vin->qlock, flags); + + int_status = rvin_get_interrupt_status(vin); + if (!int_status) + goto done; + + rvin_ack_interrupt(vin); + handled = 1; + + /* Nothing to do if capture status is 'STOPPED' */ + if (vin->state == STOPPED) { + vin_dbg(vin, "IRQ while state stopped\n"); + goto done; + } + + /* Nothing to do if capture status is 'STOPPING' */ + if (vin->state == STOPPING) { + vin_dbg(vin, "IRQ while state stopping\n"); + goto done; + } + + /* Prepare for capture and update state */ + slot = rvin_get_active_slot(vin); + sequence = vin->sequence++; + + vin_dbg(vin, "IRQ %02d: %d\tbuf0: %c buf1: %c buf2: %c\tmore: %d\n", + sequence, slot, + slot == 0 ? 'x' : vin->queue_buf[0] != NULL ? '1' : '0', + slot == 1 ? 'x' : vin->queue_buf[1] != NULL ? '1' : '0', + slot == 2 ? 'x' : vin->queue_buf[2] != NULL ? '1' : '0', + !list_empty(&vin->buf_list)); + + /* HW have written to a slot that is not prepared we are in trouble */ + if (WARN_ON((vin->queue_buf[slot] == NULL))) + goto done; + + /* Capture frame */ + vin->queue_buf[slot]->field = vin->format.field; + vin->queue_buf[slot]->sequence = sequence; + vin->queue_buf[slot]->vb2_buf.timestamp = ktime_get_ns(); + vb2_buffer_done(&vin->queue_buf[slot]->vb2_buf, VB2_BUF_STATE_DONE); + vin->queue_buf[slot] = NULL; + + /* Prepare for next frame */ + if (!rvin_fill_hw(vin)) { + + /* + * Can't supply HW with new buffers fast enough. Halt + * capture until more buffers are available. + */ + vin->state = STALLED; + + /* + * The continuous capturing requires an explicit stop + * operation when there is no buffer to be set into + * the VnMBm registers. + */ + if (vin->continuous) { + rvin_capture_off(vin); + vin_dbg(vin, "IRQ %02d: hw not ready stop\n", sequence); + } + } else { + /* + * The single capturing requires an explicit capture + * operation to fetch the next frame. + */ + if (!vin->continuous) + rvin_capture_on(vin); + } +done: + spin_unlock_irqrestore(&vin->qlock, flags); + + return IRQ_RETVAL(handled); +} + +/* Need to hold qlock before calling */ +static void return_all_buffers(struct rvin_dev *vin, + enum vb2_buffer_state state) +{ + struct rvin_buffer *buf, *node; + int i; + + for (i = 0; i < HW_BUFFER_NUM; i++) { + if (vin->queue_buf[i]) { + vb2_buffer_done(&vin->queue_buf[i]->vb2_buf, + state); + vin->queue_buf[i] = NULL; + } + } + + list_for_each_entry_safe(buf, node, &vin->buf_list, list) { + vb2_buffer_done(&buf->vb.vb2_buf, state); + list_del(&buf->list); + } +} + +static int rvin_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, + unsigned int *nplanes, unsigned int sizes[], + void *alloc_ctxs[]) + +{ + struct rvin_dev *vin = vb2_get_drv_priv(vq); + + alloc_ctxs[0] = vin->alloc_ctx; + /* Make sure the image size is large enough. */ + if (*nplanes) + return sizes[0] < vin->format.sizeimage ? -EINVAL : 0; + + *nplanes = 1; + sizes[0] = vin->format.sizeimage; + + return 0; +}; + +static int rvin_buffer_prepare(struct vb2_buffer *vb) +{ + struct rvin_dev *vin = vb2_get_drv_priv(vb->vb2_queue); + unsigned long size = vin->format.sizeimage; + + if (vb2_plane_size(vb, 0) < size) { + vin_err(vin, "buffer too small (%lu < %lu)\n", + vb2_plane_size(vb, 0), size); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, size); + + return 0; +} + +static void rvin_buffer_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct rvin_dev *vin = vb2_get_drv_priv(vb->vb2_queue); + unsigned long flags; + + spin_lock_irqsave(&vin->qlock, flags); + + list_add_tail(to_buf_list(vbuf), &vin->buf_list); + + /* + * If capture is stalled add buffer to HW and restart + * capturing if HW is ready to continue. + */ + if (vin->state == STALLED) + if (rvin_fill_hw(vin)) + rvin_capture_on(vin); + + spin_unlock_irqrestore(&vin->qlock, flags); +} + +static int rvin_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct rvin_dev *vin = vb2_get_drv_priv(vq); + struct v4l2_subdev *sd; + unsigned long flags; + int ret; + + sd = vin_to_source(vin); + v4l2_subdev_call(sd, video, s_stream, 1); + + 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); + } + + spin_unlock_irqrestore(&vin->qlock, flags); + + return ret; +} + +static void rvin_stop_streaming(struct vb2_queue *vq) +{ + struct rvin_dev *vin = vb2_get_drv_priv(vq); + struct v4l2_subdev *sd; + unsigned long flags; + int retries = 0; + + spin_lock_irqsave(&vin->qlock, flags); + + vin->state = STOPPING; + + /* Wait for streaming to stop */ + while (retries++ < RVIN_RETRIES) { + + rvin_capture_stop(vin); + + /* Check if HW is stopped */ + if (!rvin_capture_active(vin)) { + vin->state = STOPPED; + break; + } + + spin_unlock_irqrestore(&vin->qlock, flags); + msleep(RVIN_TIMEOUT_MS); + spin_lock_irqsave(&vin->qlock, flags); + } + + if (vin->state != STOPPED) { + /* + * If this happens something have gone horribly wrong. + * Set state to stopped to prevent the interrupt handler + * to make things worse... + */ + vin_err(vin, "Failed stop HW, something is seriously broken\n"); + vin->state = STOPPED; + } + + /* Release all active buffers */ + return_all_buffers(vin, VB2_BUF_STATE_ERROR); + + spin_unlock_irqrestore(&vin->qlock, flags); + + sd = vin_to_source(vin); + v4l2_subdev_call(sd, video, s_stream, 0); + + /* disable interrupts */ + rvin_disable_interrupts(vin); +} + +static struct vb2_ops rvin_qops = { + .queue_setup = rvin_queue_setup, + .buf_prepare = rvin_buffer_prepare, + .buf_queue = rvin_buffer_queue, + .start_streaming = rvin_start_streaming, + .stop_streaming = rvin_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +void rvin_dma_remove(struct rvin_dev *vin) +{ + if (!IS_ERR_OR_NULL(vin->alloc_ctx)) + vb2_dma_contig_cleanup_ctx(vin->alloc_ctx); + + mutex_destroy(&vin->lock); + + v4l2_device_unregister(&vin->v4l2_dev); +} + +int rvin_dma_probe(struct rvin_dev *vin, int irq) +{ + struct vb2_queue *q = &vin->queue; + int i, ret; + + /* Initialize the top-level structure */ + ret = v4l2_device_register(vin->dev, &vin->v4l2_dev); + if (ret) + return ret; + + mutex_init(&vin->lock); + INIT_LIST_HEAD(&vin->buf_list); + + spin_lock_init(&vin->qlock); + + vin->state = STOPPED; + + for (i = 0; i < HW_BUFFER_NUM; i++) + vin->queue_buf[i] = NULL; + + /* buffer queue */ + vin->alloc_ctx = vb2_dma_contig_init_ctx(vin->dev); + if (IS_ERR(vin->alloc_ctx)) { + ret = PTR_ERR(vin->alloc_ctx); + goto error; + } + + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_READ | VB2_DMABUF; + q->lock = &vin->lock; + q->drv_priv = vin; + q->buf_struct_size = sizeof(struct rvin_buffer); + q->ops = &rvin_qops; + q->mem_ops = &vb2_dma_contig_memops; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->min_buffers_needed = 2; + + ret = vb2_queue_init(q); + if (ret < 0) { + vin_err(vin, "failed to initialize VB2 queue\n"); + goto error; + } + + /* irq */ + ret = devm_request_irq(vin->dev, irq, rvin_irq, IRQF_SHARED, + KBUILD_MODNAME, vin); + if (ret) { + vin_err(vin, "failed to request irq\n"); + goto error; + } + + return 0; +error: + rvin_dma_remove(vin); + + return ret; +} diff --git a/drivers/media/platform/rcar-vin/rcar-v4l2.c b/drivers/media/platform/rcar-vin/rcar-v4l2.c new file mode 100644 index 000000000000..10a5c107e8b9 --- /dev/null +++ b/drivers/media/platform/rcar-vin/rcar-v4l2.c @@ -0,0 +1,874 @@ +/* + * Driver for Renesas R-Car VIN + * + * Copyright (C) 2016 Renesas Electronics Corp. + * Copyright (C) 2011-2013 Renesas Solutions Corp. + * Copyright (C) 2013 Cogent Embedded, Inc., <source@cogentembedded.com> + * Copyright (C) 2008 Magnus Damm + * + * Based on the soc-camera rcar_vin driver + * + * 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/pm_runtime.h> + +#include <media/v4l2-event.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-rect.h> + +#include "rcar-vin.h" + +#define RVIN_DEFAULT_FORMAT V4L2_PIX_FMT_YUYV +#define RVIN_MAX_WIDTH 2048 +#define RVIN_MAX_HEIGHT 2048 + +/* ----------------------------------------------------------------------------- + * Format Conversions + */ + +static const struct rvin_video_format rvin_formats[] = { + { + .fourcc = V4L2_PIX_FMT_NV16, + .bpp = 1, + }, + { + .fourcc = V4L2_PIX_FMT_YUYV, + .bpp = 2, + }, + { + .fourcc = V4L2_PIX_FMT_UYVY, + .bpp = 2, + }, + { + .fourcc = V4L2_PIX_FMT_RGB565, + .bpp = 2, + }, + { + .fourcc = V4L2_PIX_FMT_XRGB555, + .bpp = 2, + }, + { + .fourcc = V4L2_PIX_FMT_XBGR32, + .bpp = 4, + }, +}; + +const struct rvin_video_format *rvin_format_from_pixel(u32 pixelformat) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(rvin_formats); i++) + if (rvin_formats[i].fourcc == pixelformat) + return rvin_formats + i; + + return NULL; +} + +static u32 rvin_format_bytesperline(struct v4l2_pix_format *pix) +{ + const struct rvin_video_format *fmt; + + fmt = rvin_format_from_pixel(pix->pixelformat); + + if (WARN_ON(!fmt)) + return -EINVAL; + + return pix->width * fmt->bpp; +} + +static u32 rvin_format_sizeimage(struct v4l2_pix_format *pix) +{ + if (pix->pixelformat == V4L2_PIX_FMT_NV16) + return pix->bytesperline * pix->height * 2; + + return pix->bytesperline * pix->height; +} + +/* ----------------------------------------------------------------------------- + * V4L2 + */ + +static int __rvin_try_format_source(struct rvin_dev *vin, + u32 which, + struct v4l2_pix_format *pix, + struct rvin_source_fmt *source) +{ + struct v4l2_subdev *sd; + struct v4l2_subdev_pad_config *pad_cfg; + struct v4l2_subdev_format format = { + .which = which, + }; + int ret; + + sd = vin_to_source(vin); + + v4l2_fill_mbus_format(&format.format, pix, vin->source.code); + + pad_cfg = v4l2_subdev_alloc_pad_config(sd); + if (pad_cfg == NULL) + return -ENOMEM; + + format.pad = vin->src_pad_idx; + + ret = v4l2_device_call_until_err(sd->v4l2_dev, 0, pad, set_fmt, + pad_cfg, &format); + if (ret < 0) + goto cleanup; + + v4l2_fill_pix_format(pix, &format.format); + + source->width = pix->width; + source->height = pix->height; + + vin_dbg(vin, "Source resolution: %ux%u\n", source->width, + source->height); + +cleanup: + v4l2_subdev_free_pad_config(pad_cfg); + return 0; +} + +static int __rvin_try_format(struct rvin_dev *vin, + u32 which, + struct v4l2_pix_format *pix, + struct rvin_source_fmt *source) +{ + const struct rvin_video_format *info; + u32 rwidth, rheight, walign; + + /* Requested */ + rwidth = pix->width; + rheight = pix->height; + + /* + * 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; + } + + /* Always recalculate */ + pix->bytesperline = 0; + pix->sizeimage = 0; + + /* Limit to source capabilities */ + __rvin_try_format_source(vin, which, pix, source); + + /* If source can't match format try if VIN can scale */ + if (source->width != rwidth || source->height != rheight) + rvin_scale_try(vin, pix, rwidth, rheight); + + /* HW limit width to a multiple of 32 (2^5) for NV16 else 2 (2^1) */ + walign = vin->format.pixelformat == V4L2_PIX_FMT_NV16 ? 5 : 1; + + /* Limit to VIN capabilities */ + v4l_bound_align_image(&pix->width, 2, RVIN_MAX_WIDTH, walign, + &pix->height, 4, RVIN_MAX_HEIGHT, 2, 0); + + switch (pix->field) { + case V4L2_FIELD_NONE: + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + case V4L2_FIELD_INTERLACED_TB: + case V4L2_FIELD_INTERLACED_BT: + case V4L2_FIELD_INTERLACED: + break; + default: + pix->field = V4L2_FIELD_NONE; + break; + } + + pix->bytesperline = max_t(u32, pix->bytesperline, + rvin_format_bytesperline(pix)); + pix->sizeimage = max_t(u32, pix->sizeimage, + rvin_format_sizeimage(pix)); + + vin_dbg(vin, "Requested %ux%u Got %ux%u bpl: %d size: %d\n", + rwidth, rheight, pix->width, pix->height, + pix->bytesperline, pix->sizeimage); + + return 0; +} + +static int rvin_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct rvin_dev *vin = video_drvdata(file); + + strlcpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver)); + strlcpy(cap->card, "R_Car_VIN", sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", + dev_name(vin->dev)); + return 0; +} + +static int rvin_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rvin_dev *vin = video_drvdata(file); + struct rvin_source_fmt source; + + return __rvin_try_format(vin, V4L2_SUBDEV_FORMAT_TRY, &f->fmt.pix, + &source); +} + +static int rvin_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rvin_dev *vin = video_drvdata(file); + struct rvin_source_fmt source; + int ret; + + if (vb2_is_busy(&vin->queue)) + return -EBUSY; + + ret = __rvin_try_format(vin, V4L2_SUBDEV_FORMAT_ACTIVE, &f->fmt.pix, + &source); + if (ret) + return ret; + + vin->source.width = source.width; + vin->source.height = source.height; + + vin->format = f->fmt.pix; + + return 0; +} + +static int rvin_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rvin_dev *vin = video_drvdata(file); + + f->fmt.pix = vin->format; + + return 0; +} + +static int rvin_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index >= ARRAY_SIZE(rvin_formats)) + return -EINVAL; + + f->pixelformat = rvin_formats[f->index].fourcc; + + return 0; +} + +static int rvin_g_selection(struct file *file, void *fh, + struct v4l2_selection *s) +{ + struct rvin_dev *vin = video_drvdata(file); + + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + switch (s->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_CROP_DEFAULT: + s->r.left = s->r.top = 0; + s->r.width = vin->source.width; + s->r.height = vin->source.height; + break; + case V4L2_SEL_TGT_CROP: + s->r = vin->crop; + break; + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + s->r.left = s->r.top = 0; + s->r.width = vin->format.width; + s->r.height = vin->format.height; + break; + case V4L2_SEL_TGT_COMPOSE: + s->r = vin->compose; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int rvin_s_selection(struct file *file, void *fh, + struct v4l2_selection *s) +{ + struct rvin_dev *vin = video_drvdata(file); + const struct rvin_video_format *fmt; + struct v4l2_rect r = s->r; + struct v4l2_rect max_rect; + struct v4l2_rect min_rect = { + .width = 6, + .height = 2, + }; + + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + v4l2_rect_set_min_size(&r, &min_rect); + + switch (s->target) { + case V4L2_SEL_TGT_CROP: + /* Can't crop outside of source input */ + max_rect.top = max_rect.left = 0; + max_rect.width = vin->source.width; + max_rect.height = vin->source.height; + v4l2_rect_map_inside(&r, &max_rect); + + v4l_bound_align_image(&r.width, 2, vin->source.width, 1, + &r.height, 4, vin->source.height, 2, 0); + + r.top = clamp_t(s32, r.top, 0, vin->source.height - r.height); + r.left = clamp_t(s32, r.left, 0, vin->source.width - r.width); + + vin->crop = s->r = r; + + vin_dbg(vin, "Cropped %dx%d@%d:%d of %dx%d\n", + r.width, r.height, r.left, r.top, + vin->source.width, vin->source.height); + break; + case V4L2_SEL_TGT_COMPOSE: + /* Make sure compose rect fits inside output format */ + max_rect.top = max_rect.left = 0; + max_rect.width = vin->format.width; + max_rect.height = vin->format.height; + v4l2_rect_map_inside(&r, &max_rect); + + /* + * Composing is done by adding a offset to the buffer address, + * the HW wants this address to be aligned to HW_BUFFER_MASK. + * Make sure the top and left values meets this requirement. + */ + while ((r.top * vin->format.bytesperline) & HW_BUFFER_MASK) + r.top--; + + fmt = rvin_format_from_pixel(vin->format.pixelformat); + while ((r.left * fmt->bpp) & HW_BUFFER_MASK) + r.left--; + + vin->compose = s->r = r; + + vin_dbg(vin, "Compose %dx%d@%d:%d in %dx%d\n", + r.width, r.height, r.left, r.top, + vin->format.width, vin->format.height); + break; + default: + return -EINVAL; + } + + /* HW supports modifying configuration while running */ + rvin_crop_scale_comp(vin); + + return 0; +} + +static int rvin_cropcap(struct file *file, void *priv, + struct v4l2_cropcap *crop) +{ + struct rvin_dev *vin = video_drvdata(file); + struct v4l2_subdev *sd = vin_to_source(vin); + + if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + return v4l2_subdev_call(sd, video, cropcap, crop); +} + +static int rvin_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + struct rvin_dev *vin = video_drvdata(file); + struct v4l2_subdev *sd = vin_to_source(vin); + int ret; + + if (i->index != 0) + return -EINVAL; + + ret = v4l2_subdev_call(sd, video, g_input_status, &i->status); + if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV) + return ret; + + i->type = V4L2_INPUT_TYPE_CAMERA; + i->std = vin->vdev.tvnorms; + + if (v4l2_subdev_has_op(sd, pad, dv_timings_cap)) + i->capabilities = V4L2_IN_CAP_DV_TIMINGS; + + strlcpy(i->name, "Camera", sizeof(i->name)); + + return 0; +} + +static int rvin_g_input(struct file *file, void *priv, unsigned int *i) +{ + *i = 0; + return 0; +} + +static int rvin_s_input(struct file *file, void *priv, unsigned int i) +{ + if (i > 0) + return -EINVAL; + return 0; +} + +static int rvin_querystd(struct file *file, void *priv, v4l2_std_id *a) +{ + struct rvin_dev *vin = video_drvdata(file); + struct v4l2_subdev *sd = vin_to_source(vin); + + return v4l2_subdev_call(sd, video, querystd, a); +} + +static int rvin_s_std(struct file *file, void *priv, v4l2_std_id a) +{ + struct rvin_dev *vin = video_drvdata(file); + struct v4l2_subdev *sd = vin_to_source(vin); + struct v4l2_subdev_format fmt = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + struct v4l2_mbus_framefmt *mf = &fmt.format; + int ret = v4l2_subdev_call(sd, video, s_std, a); + + if (ret < 0) + return ret; + + /* Changing the standard will change the width/height */ + ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &fmt); + if (ret) { + vin_err(vin, "Failed to get initial format\n"); + return ret; + } + + vin->format.width = mf->width; + vin->format.height = mf->height; + + vin->crop.top = vin->crop.left = 0; + vin->crop.width = mf->width; + vin->crop.height = mf->height; + + vin->compose.top = vin->compose.left = 0; + vin->compose.width = mf->width; + vin->compose.height = mf->height; + + return 0; +} + +static int rvin_g_std(struct file *file, void *priv, v4l2_std_id *a) +{ + struct rvin_dev *vin = video_drvdata(file); + struct v4l2_subdev *sd = vin_to_source(vin); + + return v4l2_subdev_call(sd, video, g_std, a); +} + +static int rvin_subscribe_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_event_subscribe(fh, sub, 4, NULL); + } + return v4l2_ctrl_subscribe_event(fh, sub); +} + +static int rvin_enum_dv_timings(struct file *file, void *priv_fh, + struct v4l2_enum_dv_timings *timings) +{ + struct rvin_dev *vin = video_drvdata(file); + struct v4l2_subdev *sd = vin_to_source(vin); + int pad, ret; + + pad = timings->pad; + timings->pad = vin->src_pad_idx; + + ret = v4l2_subdev_call(sd, pad, enum_dv_timings, timings); + + timings->pad = pad; + + return ret; +} + +static int rvin_s_dv_timings(struct file *file, void *priv_fh, + struct v4l2_dv_timings *timings) +{ + struct rvin_dev *vin = video_drvdata(file); + struct v4l2_subdev *sd = vin_to_source(vin); + int err; + + err = v4l2_subdev_call(sd, + video, s_dv_timings, timings); + if (!err) { + 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 err; +} + +static int rvin_g_dv_timings(struct file *file, void *priv_fh, + struct v4l2_dv_timings *timings) +{ + struct rvin_dev *vin = video_drvdata(file); + struct v4l2_subdev *sd = vin_to_source(vin); + + return v4l2_subdev_call(sd, + video, g_dv_timings, timings); +} + +static int rvin_query_dv_timings(struct file *file, void *priv_fh, + struct v4l2_dv_timings *timings) +{ + struct rvin_dev *vin = video_drvdata(file); + struct v4l2_subdev *sd = vin_to_source(vin); + + return v4l2_subdev_call(sd, + video, query_dv_timings, timings); +} + +static int rvin_dv_timings_cap(struct file *file, void *priv_fh, + struct v4l2_dv_timings_cap *cap) +{ + struct rvin_dev *vin = video_drvdata(file); + struct v4l2_subdev *sd = vin_to_source(vin); + int pad, ret; + + pad = cap->pad; + cap->pad = vin->src_pad_idx; + + ret = v4l2_subdev_call(sd, pad, dv_timings_cap, cap); + + cap->pad = pad; + + return ret; +} + +static const struct v4l2_ioctl_ops rvin_ioctl_ops = { + .vidioc_querycap = rvin_querycap, + .vidioc_try_fmt_vid_cap = rvin_try_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = rvin_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = rvin_s_fmt_vid_cap, + .vidioc_enum_fmt_vid_cap = rvin_enum_fmt_vid_cap, + + .vidioc_g_selection = rvin_g_selection, + .vidioc_s_selection = rvin_s_selection, + + .vidioc_cropcap = rvin_cropcap, + + .vidioc_enum_input = rvin_enum_input, + .vidioc_g_input = rvin_g_input, + .vidioc_s_input = rvin_s_input, + + .vidioc_dv_timings_cap = rvin_dv_timings_cap, + .vidioc_enum_dv_timings = rvin_enum_dv_timings, + .vidioc_g_dv_timings = rvin_g_dv_timings, + .vidioc_s_dv_timings = rvin_s_dv_timings, + .vidioc_query_dv_timings = rvin_query_dv_timings, + + .vidioc_querystd = rvin_querystd, + .vidioc_g_std = rvin_g_std, + .vidioc_s_std = rvin_s_std, + + .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 = rvin_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +/* ----------------------------------------------------------------------------- + * File Operations + */ + +static int rvin_power_on(struct rvin_dev *vin) +{ + int ret; + struct v4l2_subdev *sd = vin_to_source(vin); + + pm_runtime_get_sync(vin->v4l2_dev.dev); + + ret = v4l2_subdev_call(sd, core, s_power, 1); + if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV) + return ret; + return 0; +} + +static int rvin_power_off(struct rvin_dev *vin) +{ + int ret; + struct v4l2_subdev *sd = vin_to_source(vin); + + ret = v4l2_subdev_call(sd, core, s_power, 0); + + pm_runtime_put(vin->v4l2_dev.dev); + + if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV) + return ret; + + return 0; +} + +static int rvin_initialize_device(struct file *file) +{ + struct rvin_dev *vin = video_drvdata(file); + int ret; + + struct v4l2_format f = { + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .fmt.pix = { + .width = vin->format.width, + .height = vin->format.height, + .field = vin->format.field, + .colorspace = vin->format.colorspace, + .pixelformat = vin->format.pixelformat, + }, + }; + + ret = rvin_power_on(vin); + if (ret < 0) + return ret; + + pm_runtime_enable(&vin->vdev.dev); + ret = pm_runtime_resume(&vin->vdev.dev); + if (ret < 0 && ret != -ENOSYS) + goto eresume; + + /* + * Try to configure with default parameters. Notice: this is the + * very first open, so, we cannot race against other calls, + * apart from someone else calling open() simultaneously, but + * .host_lock is protecting us against it. + */ + ret = rvin_s_fmt_vid_cap(file, NULL, &f); + if (ret < 0) + goto esfmt; + + v4l2_ctrl_handler_setup(&vin->ctrl_handler); + + return 0; +esfmt: + pm_runtime_disable(&vin->vdev.dev); +eresume: + rvin_power_off(vin); + + return ret; +} + +static int rvin_open(struct file *file) +{ + struct rvin_dev *vin = video_drvdata(file); + int ret; + + mutex_lock(&vin->lock); + + file->private_data = vin; + + ret = v4l2_fh_open(file); + if (ret) + goto unlock; + + if (!v4l2_fh_is_singular_file(file)) + goto unlock; + + if (rvin_initialize_device(file)) { + v4l2_fh_release(file); + ret = -ENODEV; + } + +unlock: + mutex_unlock(&vin->lock); + return ret; +} + +static int rvin_release(struct file *file) +{ + struct rvin_dev *vin = video_drvdata(file); + bool fh_singular; + int ret; + + mutex_lock(&vin->lock); + + /* Save the singular status before we call the clean-up helper */ + fh_singular = v4l2_fh_is_singular_file(file); + + /* the release helper will cleanup any on-going streaming */ + ret = _vb2_fop_release(file, NULL); + + /* + * If this was the last open file. + * Then de-initialize hw module. + */ + if (fh_singular) { + pm_runtime_suspend(&vin->vdev.dev); + pm_runtime_disable(&vin->vdev.dev); + rvin_power_off(vin); + } + + mutex_unlock(&vin->lock); + + return ret; +} + +static const struct v4l2_file_operations rvin_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = video_ioctl2, + .open = rvin_open, + .release = rvin_release, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .read = vb2_fop_read, +}; + +void rvin_v4l2_remove(struct rvin_dev *vin) +{ + v4l2_info(&vin->v4l2_dev, "Removing %s\n", + video_device_node_name(&vin->vdev)); + + /* Checks internaly if handlers have been init or not */ + v4l2_ctrl_handler_free(&vin->ctrl_handler); + + /* Checks internaly if vdev have been init or not */ + video_unregister_device(&vin->vdev); +} + +static void rvin_notify(struct v4l2_subdev *sd, + unsigned int notification, void *arg) +{ + struct rvin_dev *vin = + container_of(sd->v4l2_dev, struct rvin_dev, v4l2_dev); + + switch (notification) { + case V4L2_DEVICE_NOTIFY_EVENT: + v4l2_event_queue(&vin->vdev, arg); + break; + default: + break; + } +} + +int rvin_v4l2_probe(struct rvin_dev *vin) +{ + struct v4l2_subdev_format fmt = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + struct v4l2_mbus_framefmt *mf = &fmt.format; + struct video_device *vdev = &vin->vdev; + struct v4l2_subdev *sd = vin_to_source(vin); +#if defined(CONFIG_MEDIA_CONTROLLER) + int pad_idx; +#endif + int ret; + + v4l2_set_subdev_hostdata(sd, vin); + + vin->v4l2_dev.notify = rvin_notify; + + ret = v4l2_subdev_call(sd, video, g_tvnorms, &vin->vdev.tvnorms); + if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV) + return ret; + + if (vin->vdev.tvnorms == 0) { + /* Disable the STD API if there are no tvnorms defined */ + v4l2_disable_ioctl(&vin->vdev, VIDIOC_G_STD); + v4l2_disable_ioctl(&vin->vdev, VIDIOC_S_STD); + v4l2_disable_ioctl(&vin->vdev, VIDIOC_QUERYSTD); + v4l2_disable_ioctl(&vin->vdev, VIDIOC_ENUMSTD); + } + + /* Add the controls */ + /* + * Currently the subdev with the largest number of controls (13) is + * ov6550. So let's pick 16 as a hint for the control handler. Note + * that this is a hint only: too large and you waste some memory, too + * small and there is a (very) small performance hit when looking up + * controls in the internal hash. + */ + ret = v4l2_ctrl_handler_init(&vin->ctrl_handler, 16); + if (ret < 0) + return ret; + + ret = v4l2_ctrl_add_handler(&vin->ctrl_handler, sd->ctrl_handler, NULL); + if (ret < 0) + return ret; + + /* video node */ + vdev->fops = &rvin_fops; + vdev->v4l2_dev = &vin->v4l2_dev; + vdev->queue = &vin->queue; + strlcpy(vdev->name, KBUILD_MODNAME, sizeof(vdev->name)); + vdev->release = video_device_release_empty; + vdev->ioctl_ops = &rvin_ioctl_ops; + vdev->lock = &vin->lock; + vdev->ctrl_handler = &vin->ctrl_handler; + vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | + V4L2_CAP_READWRITE; + + vin->src_pad_idx = 0; +#if defined(CONFIG_MEDIA_CONTROLLER) + 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; +#endif + fmt.pad = vin->src_pad_idx; + + /* Try to improve our guess of a reasonable window format */ + ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &fmt); + if (ret) { + vin_err(vin, "Failed to get initial format\n"); + return ret; + } + + /* Set default format */ + vin->format.width = mf->width; + vin->format.height = mf->height; + vin->format.colorspace = mf->colorspace; + vin->format.field = mf->field; + vin->format.pixelformat = RVIN_DEFAULT_FORMAT; + + + /* Set initial crop and compose */ + vin->crop.top = vin->crop.left = 0; + vin->crop.width = mf->width; + vin->crop.height = mf->height; + + vin->compose.top = vin->compose.left = 0; + vin->compose.width = mf->width; + vin->compose.height = mf->height; + + ret = video_register_device(&vin->vdev, VFL_TYPE_GRABBER, -1); + if (ret) { + vin_err(vin, "Failed to register video device\n"); + return ret; + } + + video_set_drvdata(&vin->vdev, vin); + + v4l2_info(&vin->v4l2_dev, "Device registered as %s\n", + video_device_node_name(&vin->vdev)); + + return ret; +} diff --git a/drivers/media/platform/rcar-vin/rcar-vin.h b/drivers/media/platform/rcar-vin/rcar-vin.h new file mode 100644 index 000000000000..a6dd6db246ab --- /dev/null +++ b/drivers/media/platform/rcar-vin/rcar-vin.h @@ -0,0 +1,165 @@ +/* + * Driver for Renesas R-Car VIN + * + * Copyright (C) 2016 Renesas Electronics Corp. + * Copyright (C) 2011-2013 Renesas Solutions Corp. + * Copyright (C) 2013 Cogent Embedded, Inc., <source@cogentembedded.com> + * Copyright (C) 2008 Magnus Damm + * + * Based on the soc-camera rcar_vin driver + * + * 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 __RCAR_VIN__ +#define __RCAR_VIN__ + +#include <media/v4l2-async.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-device.h> +#include <media/videobuf2-v4l2.h> + +/* Number of HW buffers */ +#define HW_BUFFER_NUM 3 + +/* Address alignment mask for HW buffers */ +#define HW_BUFFER_MASK 0x7f + +enum chip_id { + RCAR_GEN2, + RCAR_H1, + RCAR_M1, +}; + +/** + * STOPPED - No operation in progress + * RUNNING - Operation in progress have buffers + * STALLED - No operation in progress have no buffers + * STOPPING - Stopping operation + */ +enum rvin_dma_state { + STOPPED = 0, + RUNNING, + STALLED, + STOPPING, +}; + +/** + * struct rvin_source_fmt - Source information + * @code: Media bus format from source + * @width: Width from source + * @height: Height from source + */ +struct rvin_source_fmt { + u32 code; + u32 width; + u32 height; +}; + +/** + * struct rvin_video_format - Data format stored in memory + * @fourcc: Pixelformat + * @bpp: Bytes per pixel + */ +struct rvin_video_format { + u32 fourcc; + u8 bpp; +}; + +struct rvin_graph_entity { + struct device_node *node; + struct media_entity *entity; + + struct v4l2_async_subdev asd; + struct v4l2_subdev *subdev; +}; + +/** + * struct rvin_dev - Renesas VIN device structure + * @dev: (OF) device + * @base: device I/O register space remapped to virtual memory + * @chip: type of VIN chip + * @mbus_cfg media bus configuration + * + * @vdev: V4L2 video device associated with VIN + * @v4l2_dev: V4L2 device + * @src_pad_idx: source pad index for media controller drivers + * @ctrl_handler: V4L2 control handler + * @notifier: V4L2 asynchronous subdevs notifier + * @entity: entity in the DT for subdevice + * + * @lock: protects @queue + * @queue: vb2 buffers queue + * @alloc_ctx: allocation context for the vb2 @queue + * + * @qlock: protects @queue_buf, @buf_list, @continuous, @sequence + * @state + * @queue_buf: Keeps track of buffers given to HW slot + * @buf_list: list of queued buffers + * @continuous: tracks if active operation is continuous or single mode + * @sequence: V4L2 buffers sequence number + * @state: keeps track of operation state + * + * @source: active format from the video source + * @format: active V4L2 pixel format + * + * @crop: active cropping + * @compose: active composing + */ +struct rvin_dev { + struct device *dev; + void __iomem *base; + enum chip_id chip; + struct v4l2_mbus_config mbus_cfg; + + struct video_device vdev; + struct v4l2_device v4l2_dev; + int src_pad_idx; + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_async_notifier notifier; + struct rvin_graph_entity entity; + + struct mutex lock; + struct vb2_queue queue; + struct vb2_alloc_ctx *alloc_ctx; + + spinlock_t qlock; + struct vb2_v4l2_buffer *queue_buf[HW_BUFFER_NUM]; + struct list_head buf_list; + bool continuous; + unsigned int sequence; + enum rvin_dma_state state; + + struct rvin_source_fmt source; + struct v4l2_pix_format format; + + struct v4l2_rect crop; + struct v4l2_rect compose; +}; + +#define vin_to_source(vin) vin->entity.subdev + +/* Debug */ +#define vin_dbg(d, fmt, arg...) dev_dbg(d->dev, fmt, ##arg) +#define vin_info(d, fmt, arg...) dev_info(d->dev, fmt, ##arg) +#define vin_warn(d, fmt, arg...) dev_warn(d->dev, fmt, ##arg) +#define vin_err(d, fmt, arg...) dev_err(d->dev, fmt, ##arg) + +int rvin_dma_probe(struct rvin_dev *vin, int irq); +void rvin_dma_remove(struct rvin_dev *vin); + +int rvin_v4l2_probe(struct rvin_dev *vin); +void rvin_v4l2_remove(struct rvin_dev *vin); + +const struct rvin_video_format *rvin_format_from_pixel(u32 pixelformat); + +/* Cropping, composing and scaling */ +void rvin_scale_try(struct rvin_dev *vin, struct v4l2_pix_format *pix, + u32 width, u32 height); +void rvin_crop_scale_comp(struct rvin_dev *vin); + +#endif diff --git a/drivers/media/platform/s5p-g2d/g2d.c b/drivers/media/platform/s5p-g2d/g2d.c index 612d1ea514f1..d3e3469db8de 100644 --- a/drivers/media/platform/s5p-g2d/g2d.c +++ b/drivers/media/platform/s5p-g2d/g2d.c @@ -681,6 +681,7 @@ static int g2d_probe(struct platform_device *pdev) goto put_clk_gate; } + vb2_dma_contig_set_max_seg_size(&pdev->dev, DMA_BIT_MASK(32)); dev->alloc_ctx = vb2_dma_contig_init_ctx(&pdev->dev); if (IS_ERR(dev->alloc_ctx)) { ret = PTR_ERR(dev->alloc_ctx); @@ -757,6 +758,7 @@ static int g2d_remove(struct platform_device *pdev) video_unregister_device(dev->vfd); v4l2_device_unregister(&dev->v4l2_dev); vb2_dma_contig_cleanup_ctx(dev->alloc_ctx); + vb2_dma_contig_clear_max_seg_size(&pdev->dev); clk_unprepare(dev->gate); clk_put(dev->gate); clk_unprepare(dev->clk); diff --git a/drivers/media/platform/s5p-jpeg/jpeg-core.c b/drivers/media/platform/s5p-jpeg/jpeg-core.c index caa19b408551..17bc94092864 100644 --- a/drivers/media/platform/s5p-jpeg/jpeg-core.c +++ b/drivers/media/platform/s5p-jpeg/jpeg-core.c @@ -2843,6 +2843,7 @@ static int s5p_jpeg_probe(struct platform_device *pdev) goto device_register_rollback; } + vb2_dma_contig_set_max_seg_size(&pdev->dev, DMA_BIT_MASK(32)); jpeg->alloc_ctx = vb2_dma_contig_init_ctx(&pdev->dev); if (IS_ERR(jpeg->alloc_ctx)) { v4l2_err(&jpeg->v4l2_dev, "Failed to init memory allocator\n"); @@ -2942,6 +2943,7 @@ static int s5p_jpeg_remove(struct platform_device *pdev) video_unregister_device(jpeg->vfd_decoder); video_unregister_device(jpeg->vfd_encoder); vb2_dma_contig_cleanup_ctx(jpeg->alloc_ctx); + vb2_dma_contig_clear_max_seg_size(&pdev->dev); v4l2_m2m_release(jpeg->m2m_dev); v4l2_device_unregister(&jpeg->v4l2_dev); diff --git a/drivers/media/platform/s5p-mfc/s5p_mfc.c b/drivers/media/platform/s5p-mfc/s5p_mfc.c index b16466fe35ee..6ee620ee8cd5 100644 --- a/drivers/media/platform/s5p-mfc/s5p_mfc.c +++ b/drivers/media/platform/s5p-mfc/s5p_mfc.c @@ -22,6 +22,7 @@ #include <media/v4l2-event.h> #include <linux/workqueue.h> #include <linux/of.h> +#include <linux/of_reserved_mem.h> #include <media/videobuf2-v4l2.h> #include "s5p_mfc_common.h" #include "s5p_mfc_ctrl.h" @@ -29,6 +30,7 @@ #include "s5p_mfc_dec.h" #include "s5p_mfc_enc.h" #include "s5p_mfc_intr.h" +#include "s5p_mfc_iommu.h" #include "s5p_mfc_opr.h" #include "s5p_mfc_cmd.h" #include "s5p_mfc_pm.h" @@ -1043,55 +1045,94 @@ static const struct v4l2_file_operations s5p_mfc_fops = { .mmap = s5p_mfc_mmap, }; -static int match_child(struct device *dev, void *data) +/* DMA memory related helper functions */ +static void s5p_mfc_memdev_release(struct device *dev) { - if (!dev_name(dev)) - return 0; - return !strcmp(dev_name(dev), (char *)data); + of_reserved_mem_device_release(dev); } -static void *mfc_get_drv_data(struct platform_device *pdev); - -static int s5p_mfc_alloc_memdevs(struct s5p_mfc_dev *dev) +static struct device *s5p_mfc_alloc_memdev(struct device *dev, + const char *name, unsigned int idx) { - unsigned int mem_info[2] = { }; + struct device *child; + int ret; - dev->mem_dev_l = devm_kzalloc(&dev->plat_dev->dev, - sizeof(struct device), GFP_KERNEL); - if (!dev->mem_dev_l) { - mfc_err("Not enough memory\n"); - return -ENOMEM; - } - device_initialize(dev->mem_dev_l); - of_property_read_u32_array(dev->plat_dev->dev.of_node, - "samsung,mfc-l", mem_info, 2); - if (dma_declare_coherent_memory(dev->mem_dev_l, mem_info[0], - mem_info[0], mem_info[1], - DMA_MEMORY_MAP | DMA_MEMORY_EXCLUSIVE) == 0) { - mfc_err("Failed to declare coherent memory for\n" - "MFC device\n"); - return -ENOMEM; + child = devm_kzalloc(dev, sizeof(struct device), GFP_KERNEL); + if (!child) + return NULL; + + device_initialize(child); + dev_set_name(child, "%s:%s", dev_name(dev), name); + child->parent = dev; + child->bus = dev->bus; + child->coherent_dma_mask = dev->coherent_dma_mask; + child->dma_mask = dev->dma_mask; + child->release = s5p_mfc_memdev_release; + + if (device_add(child) == 0) { + ret = of_reserved_mem_device_init_by_idx(child, dev->of_node, + idx); + if (ret == 0) + return child; } - dev->mem_dev_r = devm_kzalloc(&dev->plat_dev->dev, - sizeof(struct device), GFP_KERNEL); - if (!dev->mem_dev_r) { - mfc_err("Not enough memory\n"); - return -ENOMEM; + put_device(child); + return NULL; +} + +static int s5p_mfc_configure_dma_memory(struct s5p_mfc_dev *mfc_dev) +{ + struct device *dev = &mfc_dev->plat_dev->dev; + + /* + * When IOMMU is available, we cannot use the default configuration, + * because of MFC firmware requirements: address space limited to + * 256M and non-zero default start address. + * This is still simplified, not optimal configuration, but for now + * IOMMU core doesn't allow to configure device's IOMMUs channel + * separately. + */ + if (exynos_is_iommu_available(dev)) { + int ret = exynos_configure_iommu(dev, S5P_MFC_IOMMU_DMA_BASE, + S5P_MFC_IOMMU_DMA_SIZE); + if (ret == 0) + mfc_dev->mem_dev_l = mfc_dev->mem_dev_r = dev; + return ret; } - device_initialize(dev->mem_dev_r); - of_property_read_u32_array(dev->plat_dev->dev.of_node, - "samsung,mfc-r", mem_info, 2); - if (dma_declare_coherent_memory(dev->mem_dev_r, mem_info[0], - mem_info[0], mem_info[1], - DMA_MEMORY_MAP | DMA_MEMORY_EXCLUSIVE) == 0) { - pr_err("Failed to declare coherent memory for\n" - "MFC device\n"); - return -ENOMEM; + + /* + * Create and initialize virtual devices for accessing + * reserved memory regions. + */ + mfc_dev->mem_dev_l = s5p_mfc_alloc_memdev(dev, "left", + MFC_BANK1_ALLOC_CTX); + if (!mfc_dev->mem_dev_l) + return -ENODEV; + mfc_dev->mem_dev_r = s5p_mfc_alloc_memdev(dev, "right", + MFC_BANK2_ALLOC_CTX); + if (!mfc_dev->mem_dev_r) { + device_unregister(mfc_dev->mem_dev_l); + return -ENODEV; } + return 0; } +static void s5p_mfc_unconfigure_dma_memory(struct s5p_mfc_dev *mfc_dev) +{ + struct device *dev = &mfc_dev->plat_dev->dev; + + if (exynos_is_iommu_available(dev)) { + exynos_unconfigure_iommu(dev); + return; + } + + device_unregister(mfc_dev->mem_dev_l); + device_unregister(mfc_dev->mem_dev_r); +} + +static void *mfc_get_drv_data(struct platform_device *pdev); + /* MFC probe function */ static int s5p_mfc_probe(struct platform_device *pdev) { @@ -1117,12 +1158,6 @@ static int s5p_mfc_probe(struct platform_device *pdev) dev->variant = mfc_get_drv_data(pdev); - ret = s5p_mfc_init_pm(dev); - if (ret < 0) { - dev_err(&pdev->dev, "failed to get mfc clock source\n"); - return ret; - } - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); dev->regs_base = devm_ioremap_resource(&pdev->dev, res); @@ -1143,32 +1178,25 @@ static int s5p_mfc_probe(struct platform_device *pdev) goto err_res; } - if (pdev->dev.of_node) { - ret = s5p_mfc_alloc_memdevs(dev); - if (ret < 0) - goto err_res; - } else { - dev->mem_dev_l = device_find_child(&dev->plat_dev->dev, - "s5p-mfc-l", match_child); - if (!dev->mem_dev_l) { - mfc_err("Mem child (L) device get failed\n"); - ret = -ENODEV; - goto err_res; - } - dev->mem_dev_r = device_find_child(&dev->plat_dev->dev, - "s5p-mfc-r", match_child); - if (!dev->mem_dev_r) { - mfc_err("Mem child (R) device get failed\n"); - ret = -ENODEV; - goto err_res; - } + ret = s5p_mfc_configure_dma_memory(dev); + if (ret < 0) { + dev_err(&pdev->dev, "failed to configure DMA memory\n"); + return ret; } + ret = s5p_mfc_init_pm(dev); + if (ret < 0) { + dev_err(&pdev->dev, "failed to get mfc clock source\n"); + return ret; + } + + vb2_dma_contig_set_max_seg_size(dev->mem_dev_l, DMA_BIT_MASK(32)); dev->alloc_ctx[0] = vb2_dma_contig_init_ctx(dev->mem_dev_l); if (IS_ERR(dev->alloc_ctx[0])) { ret = PTR_ERR(dev->alloc_ctx[0]); goto err_res; } + vb2_dma_contig_set_max_seg_size(dev->mem_dev_r, DMA_BIT_MASK(32)); dev->alloc_ctx[1] = vb2_dma_contig_init_ctx(dev->mem_dev_r); if (IS_ERR(dev->alloc_ctx[1])) { ret = PTR_ERR(dev->alloc_ctx[1]); @@ -1201,14 +1229,6 @@ static int s5p_mfc_probe(struct platform_device *pdev) vfd->vfl_dir = VFL_DIR_M2M; snprintf(vfd->name, sizeof(vfd->name), "%s", S5P_MFC_DEC_NAME); dev->vfd_dec = vfd; - ret = video_register_device(vfd, VFL_TYPE_GRABBER, 0); - if (ret) { - v4l2_err(&dev->v4l2_dev, "Failed to register video device\n"); - video_device_release(vfd); - goto err_dec_reg; - } - v4l2_info(&dev->v4l2_dev, - "decoder registered as /dev/video%d\n", vfd->num); video_set_drvdata(vfd, dev); /* encoder */ @@ -1226,14 +1246,6 @@ static int s5p_mfc_probe(struct platform_device *pdev) vfd->vfl_dir = VFL_DIR_M2M; snprintf(vfd->name, sizeof(vfd->name), "%s", S5P_MFC_ENC_NAME); dev->vfd_enc = vfd; - ret = video_register_device(vfd, VFL_TYPE_GRABBER, 0); - if (ret) { - v4l2_err(&dev->v4l2_dev, "Failed to register video device\n"); - video_device_release(vfd); - goto err_enc_reg; - } - v4l2_info(&dev->v4l2_dev, - "encoder registered as /dev/video%d\n", vfd->num); video_set_drvdata(vfd, dev); platform_set_drvdata(pdev, dev); @@ -1250,15 +1262,34 @@ static int s5p_mfc_probe(struct platform_device *pdev) s5p_mfc_init_hw_cmds(dev); s5p_mfc_init_regs(dev); + /* Register decoder and encoder */ + ret = video_register_device(dev->vfd_dec, VFL_TYPE_GRABBER, 0); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to register video device\n"); + video_device_release(dev->vfd_dec); + goto err_dec_reg; + } + v4l2_info(&dev->v4l2_dev, + "decoder registered as /dev/video%d\n", dev->vfd_dec->num); + + ret = video_register_device(dev->vfd_enc, VFL_TYPE_GRABBER, 0); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to register video device\n"); + video_device_release(dev->vfd_enc); + goto err_enc_reg; + } + v4l2_info(&dev->v4l2_dev, + "encoder registered as /dev/video%d\n", dev->vfd_enc->num); + pr_debug("%s--\n", __func__); return 0; /* Deinit MFC if probe had failed */ err_enc_reg: - video_device_release(dev->vfd_enc); -err_enc_alloc: video_unregister_device(dev->vfd_dec); err_dec_reg: + video_device_release(dev->vfd_enc); +err_enc_alloc: video_device_release(dev->vfd_dec); err_dec_alloc: v4l2_device_unregister(&dev->v4l2_dev); @@ -1293,10 +1324,9 @@ static int s5p_mfc_remove(struct platform_device *pdev) s5p_mfc_release_firmware(dev); vb2_dma_contig_cleanup_ctx(dev->alloc_ctx[0]); vb2_dma_contig_cleanup_ctx(dev->alloc_ctx[1]); - if (pdev->dev.of_node) { - put_device(dev->mem_dev_l); - put_device(dev->mem_dev_r); - } + s5p_mfc_unconfigure_dma_memory(dev); + vb2_dma_contig_clear_max_seg_size(dev->mem_dev_l); + vb2_dma_contig_clear_max_seg_size(dev->mem_dev_r); s5p_mfc_final_pm(dev); return 0; diff --git a/drivers/media/platform/s5p-mfc/s5p_mfc_dec.c b/drivers/media/platform/s5p-mfc/s5p_mfc_dec.c index f2d6376ce618..a01a373a4c4f 100644 --- a/drivers/media/platform/s5p-mfc/s5p_mfc_dec.c +++ b/drivers/media/platform/s5p-mfc/s5p_mfc_dec.c @@ -474,7 +474,6 @@ static int reqbufs_output(struct s5p_mfc_dev *dev, struct s5p_mfc_ctx *ctx, ret = vb2_reqbufs(&ctx->vq_src, reqbufs); if (ret) goto out; - s5p_mfc_close_mfc_inst(dev, ctx); ctx->src_bufs_cnt = 0; ctx->output_state = QUEUE_FREE; } else if (ctx->output_state == QUEUE_FREE) { @@ -573,7 +572,7 @@ static int vidioc_reqbufs(struct file *file, void *priv, struct s5p_mfc_ctx *ctx = fh_to_ctx(priv); if (reqbufs->memory != V4L2_MEMORY_MMAP) { - mfc_err("Only V4L2_MEMORY_MAP is supported\n"); + mfc_err("Only V4L2_MEMORY_MMAP is supported\n"); return -EINVAL; } diff --git a/drivers/media/platform/s5p-mfc/s5p_mfc_enc.c b/drivers/media/platform/s5p-mfc/s5p_mfc_enc.c index 034b5c1d35a1..2f76aba4cc78 100644 --- a/drivers/media/platform/s5p-mfc/s5p_mfc_enc.c +++ b/drivers/media/platform/s5p-mfc/s5p_mfc_enc.c @@ -1043,10 +1043,6 @@ static int vidioc_try_fmt(struct file *file, void *priv, struct v4l2_format *f) mfc_err("failed to try output format\n"); return -EINVAL; } - if (pix_fmt_mp->plane_fmt[0].sizeimage == 0) { - mfc_err("must be set encoding output size\n"); - return -EINVAL; - } if ((dev->variant->version_bit & fmt->versions) == 0) { mfc_err("Unsupported format by this MFC version.\n"); return -EINVAL; @@ -1060,11 +1056,6 @@ static int vidioc_try_fmt(struct file *file, void *priv, struct v4l2_format *f) mfc_err("failed to try output format\n"); return -EINVAL; } - - if (fmt->num_planes != pix_fmt_mp->num_planes) { - mfc_err("failed to try output format\n"); - return -EINVAL; - } if ((dev->variant->version_bit & fmt->versions) == 0) { mfc_err("Unsupported format by this MFC version.\n"); return -EINVAL; @@ -1144,7 +1135,10 @@ static int vidioc_reqbufs(struct file *file, void *priv, return -EINVAL; if (reqbufs->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { if (reqbufs->count == 0) { + mfc_debug(2, "Freeing buffers\n"); ret = vb2_reqbufs(&ctx->vq_dst, reqbufs); + s5p_mfc_hw_call(dev->mfc_ops, release_codec_buffers, + ctx); ctx->capture_state = QUEUE_FREE; return ret; } diff --git a/drivers/media/platform/s5p-mfc/s5p_mfc_iommu.h b/drivers/media/platform/s5p-mfc/s5p_mfc_iommu.h new file mode 100644 index 000000000000..6962132ae8fa --- /dev/null +++ b/drivers/media/platform/s5p-mfc/s5p_mfc_iommu.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co.Ltd + * Authors: Marek Szyprowski <m.szyprowski@samsung.com> + * + * 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 S5P_MFC_IOMMU_H_ +#define S5P_MFC_IOMMU_H_ + +#define S5P_MFC_IOMMU_DMA_BASE 0x20000000lu +#define S5P_MFC_IOMMU_DMA_SIZE SZ_256M + +#if defined(CONFIG_EXYNOS_IOMMU) && defined(CONFIG_ARM_DMA_USE_IOMMU) + +#include <asm/dma-iommu.h> + +static inline bool exynos_is_iommu_available(struct device *dev) +{ + return dev->archdata.iommu != NULL; +} + +static inline void exynos_unconfigure_iommu(struct device *dev) +{ + struct dma_iommu_mapping *mapping = to_dma_iommu_mapping(dev); + + arm_iommu_detach_device(dev); + arm_iommu_release_mapping(mapping); +} + +static inline int exynos_configure_iommu(struct device *dev, + unsigned int base, unsigned int size) +{ + struct dma_iommu_mapping *mapping = NULL; + int ret; + + /* Disable the default mapping created by device core */ + if (to_dma_iommu_mapping(dev)) + exynos_unconfigure_iommu(dev); + + mapping = arm_iommu_create_mapping(dev->bus, base, size); + if (IS_ERR(mapping)) { + pr_warn("Failed to create IOMMU mapping for device %s\n", + dev_name(dev)); + return PTR_ERR(mapping); + } + + ret = arm_iommu_attach_device(dev, mapping); + if (ret) { + pr_warn("Failed to attached device %s to IOMMU_mapping\n", + dev_name(dev)); + arm_iommu_release_mapping(mapping); + return ret; + } + + return 0; +} + +#else + +static inline bool exynos_is_iommu_available(struct device *dev) +{ + return false; +} + +static inline int exynos_configure_iommu(struct device *dev, + unsigned int base, unsigned int size) +{ + return -ENOSYS; +} + +static inline void exynos_unconfigure_iommu(struct device *dev) { } + +#endif + +#endif /* S5P_MFC_IOMMU_H_ */ diff --git a/drivers/media/platform/s5p-mfc/s5p_mfc_pm.c b/drivers/media/platform/s5p-mfc/s5p_mfc_pm.c index 5f97a3398c11..9f7522104333 100644 --- a/drivers/media/platform/s5p-mfc/s5p_mfc_pm.c +++ b/drivers/media/platform/s5p-mfc/s5p_mfc_pm.c @@ -54,6 +54,7 @@ int s5p_mfc_init_pm(struct s5p_mfc_dev *dev) pm->clock = clk_get(&dev->plat_dev->dev, MFC_SCLK_NAME); if (IS_ERR(pm->clock)) { mfc_info("Failed to get MFC special clock control\n"); + pm->clock = NULL; } else { clk_set_rate(pm->clock, MFC_SCLK_RATE); ret = clk_prepare_enable(pm->clock); diff --git a/drivers/media/platform/s5p-tv/mixer_video.c b/drivers/media/platform/s5p-tv/mixer_video.c index 7ab5578a0405..123d27107f60 100644 --- a/drivers/media/platform/s5p-tv/mixer_video.c +++ b/drivers/media/platform/s5p-tv/mixer_video.c @@ -80,6 +80,7 @@ int mxr_acquire_video(struct mxr_device *mdev, goto fail; } + vb2_dma_contig_set_max_seg_size(mdev->dev, DMA_BIT_MASK(32)); mdev->alloc_ctx = vb2_dma_contig_init_ctx(mdev->dev); if (IS_ERR(mdev->alloc_ctx)) { mxr_err(mdev, "could not acquire vb2 allocator\n"); @@ -152,6 +153,7 @@ void mxr_release_video(struct mxr_device *mdev) kfree(mdev->output[i]); vb2_dma_contig_cleanup_ctx(mdev->alloc_ctx); + vb2_dma_contig_clear_max_seg_size(mdev->dev); v4l2_device_unregister(&mdev->v4l2_dev); } diff --git a/drivers/media/platform/soc_camera/Kconfig b/drivers/media/platform/soc_camera/Kconfig index 83029a4854ae..39f66414f621 100644 --- a/drivers/media/platform/soc_camera/Kconfig +++ b/drivers/media/platform/soc_camera/Kconfig @@ -25,8 +25,8 @@ config VIDEO_PXA27x ---help--- This is a v4l2 driver for the PXA27x Quick Capture Interface -config VIDEO_RCAR_VIN - tristate "R-Car Video Input (VIN) support" +config VIDEO_RCAR_VIN_OLD + tristate "R-Car Video Input (VIN) support (DEPRECATED)" depends on VIDEO_DEV && SOC_CAMERA depends on ARCH_RENESAS || COMPILE_TEST depends on HAS_DMA diff --git a/drivers/media/platform/soc_camera/Makefile b/drivers/media/platform/soc_camera/Makefile index 7ee71ae231c7..7703cb7ce456 100644 --- a/drivers/media/platform/soc_camera/Makefile +++ b/drivers/media/platform/soc_camera/Makefile @@ -10,4 +10,4 @@ obj-$(CONFIG_VIDEO_ATMEL_ISI) += atmel-isi.o obj-$(CONFIG_VIDEO_PXA27x) += pxa_camera.o obj-$(CONFIG_VIDEO_SH_MOBILE_CEU) += sh_mobile_ceu_camera.o obj-$(CONFIG_VIDEO_SH_MOBILE_CSI2) += sh_mobile_csi2.o -obj-$(CONFIG_VIDEO_RCAR_VIN) += rcar_vin.o +obj-$(CONFIG_VIDEO_RCAR_VIN_OLD) += rcar_vin.o diff --git a/drivers/media/platform/sti/bdisp/bdisp-filter.h b/drivers/media/platform/sti/bdisp/bdisp-filter.h index fc8c54f725ad..53e52fb4127f 100644 --- a/drivers/media/platform/sti/bdisp/bdisp-filter.h +++ b/drivers/media/platform/sti/bdisp/bdisp-filter.h @@ -19,178 +19,6 @@ struct bdisp_filter_h_spec { const u16 max; const u8 coef[BDISP_HF_NB]; }; - -static const struct bdisp_filter_h_spec bdisp_h_spec[] = { - { - .min = 0, - .max = 921, - .coef = { - 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xff, 0x07, 0x3d, 0xfc, 0x01, 0x00, - 0x00, 0x01, 0xfd, 0x11, 0x36, 0xf9, 0x02, 0x00, - 0x00, 0x01, 0xfb, 0x1b, 0x2e, 0xf9, 0x02, 0x00, - 0x00, 0x01, 0xf9, 0x26, 0x26, 0xf9, 0x01, 0x00, - 0x00, 0x02, 0xf9, 0x30, 0x19, 0xfb, 0x01, 0x00, - 0x00, 0x02, 0xf9, 0x39, 0x0e, 0xfd, 0x01, 0x00, - 0x00, 0x01, 0xfc, 0x3e, 0x06, 0xff, 0x00, 0x00 - } - }, - { - .min = 921, - .max = 1024, - .coef = { - 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, - 0xff, 0x03, 0xfd, 0x08, 0x3e, 0xf9, 0x04, 0xfe, - 0xfd, 0x06, 0xf8, 0x13, 0x3b, 0xf4, 0x07, 0xfc, - 0xfb, 0x08, 0xf5, 0x1f, 0x34, 0xf1, 0x09, 0xfb, - 0xfb, 0x09, 0xf2, 0x2b, 0x2a, 0xf1, 0x09, 0xfb, - 0xfb, 0x09, 0xf2, 0x35, 0x1e, 0xf4, 0x08, 0xfb, - 0xfc, 0x07, 0xf5, 0x3c, 0x12, 0xf7, 0x06, 0xfd, - 0xfe, 0x04, 0xfa, 0x3f, 0x07, 0xfc, 0x03, 0xff - } - }, - { - .min = 1024, - .max = 1126, - .coef = { - 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, - 0xff, 0x03, 0xfd, 0x08, 0x3e, 0xf9, 0x04, 0xfe, - 0xfd, 0x06, 0xf8, 0x13, 0x3b, 0xf4, 0x07, 0xfc, - 0xfb, 0x08, 0xf5, 0x1f, 0x34, 0xf1, 0x09, 0xfb, - 0xfb, 0x09, 0xf2, 0x2b, 0x2a, 0xf1, 0x09, 0xfb, - 0xfb, 0x09, 0xf2, 0x35, 0x1e, 0xf4, 0x08, 0xfb, - 0xfc, 0x07, 0xf5, 0x3c, 0x12, 0xf7, 0x06, 0xfd, - 0xfe, 0x04, 0xfa, 0x3f, 0x07, 0xfc, 0x03, 0xff - } - }, - { - .min = 1126, - .max = 1228, - .coef = { - 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, - 0xff, 0x03, 0xfd, 0x08, 0x3e, 0xf9, 0x04, 0xfe, - 0xfd, 0x06, 0xf8, 0x13, 0x3b, 0xf4, 0x07, 0xfc, - 0xfb, 0x08, 0xf5, 0x1f, 0x34, 0xf1, 0x09, 0xfb, - 0xfb, 0x09, 0xf2, 0x2b, 0x2a, 0xf1, 0x09, 0xfb, - 0xfb, 0x09, 0xf2, 0x35, 0x1e, 0xf4, 0x08, 0xfb, - 0xfc, 0x07, 0xf5, 0x3c, 0x12, 0xf7, 0x06, 0xfd, - 0xfe, 0x04, 0xfa, 0x3f, 0x07, 0xfc, 0x03, 0xff - } - }, - { - .min = 1228, - .max = 1331, - .coef = { - 0xfd, 0x04, 0xfc, 0x05, 0x39, 0x05, 0xfc, 0x04, - 0xfc, 0x06, 0xf9, 0x0c, 0x39, 0xfe, 0x00, 0x02, - 0xfb, 0x08, 0xf6, 0x17, 0x35, 0xf9, 0x02, 0x00, - 0xfc, 0x08, 0xf4, 0x20, 0x30, 0xf4, 0x05, 0xff, - 0xfd, 0x07, 0xf4, 0x29, 0x28, 0xf3, 0x07, 0xfd, - 0xff, 0x05, 0xf5, 0x31, 0x1f, 0xf3, 0x08, 0xfc, - 0x00, 0x02, 0xf9, 0x38, 0x14, 0xf6, 0x08, 0xfb, - 0x02, 0x00, 0xff, 0x3a, 0x0b, 0xf8, 0x06, 0xfc - } - }, - { - .min = 1331, - .max = 1433, - .coef = { - 0xfc, 0x06, 0xf9, 0x09, 0x34, 0x09, 0xf9, 0x06, - 0xfd, 0x07, 0xf7, 0x10, 0x32, 0x02, 0xfc, 0x05, - 0xfe, 0x07, 0xf6, 0x17, 0x2f, 0xfc, 0xff, 0x04, - 0xff, 0x06, 0xf5, 0x20, 0x2a, 0xf9, 0x01, 0x02, - 0x00, 0x04, 0xf6, 0x27, 0x25, 0xf6, 0x04, 0x00, - 0x02, 0x01, 0xf9, 0x2d, 0x1d, 0xf5, 0x06, 0xff, - 0x04, 0xff, 0xfd, 0x31, 0x15, 0xf5, 0x07, 0xfe, - 0x05, 0xfc, 0x02, 0x35, 0x0d, 0xf7, 0x07, 0xfd - } - }, - { - .min = 1433, - .max = 1536, - .coef = { - 0xfe, 0x06, 0xf8, 0x0b, 0x30, 0x0b, 0xf8, 0x06, - 0xff, 0x06, 0xf7, 0x12, 0x2d, 0x05, 0xfa, 0x06, - 0x00, 0x04, 0xf6, 0x18, 0x2c, 0x00, 0xfc, 0x06, - 0x01, 0x02, 0xf7, 0x1f, 0x27, 0xfd, 0xff, 0x04, - 0x03, 0x00, 0xf9, 0x24, 0x24, 0xf9, 0x00, 0x03, - 0x04, 0xff, 0xfd, 0x29, 0x1d, 0xf7, 0x02, 0x01, - 0x06, 0xfc, 0x00, 0x2d, 0x17, 0xf6, 0x04, 0x00, - 0x06, 0xfa, 0x05, 0x30, 0x0f, 0xf7, 0x06, 0xff - } - }, - { - .min = 1536, - .max = 2048, - .coef = { - 0x05, 0xfd, 0xfb, 0x13, 0x25, 0x13, 0xfb, 0xfd, - 0x05, 0xfc, 0xfd, 0x17, 0x24, 0x0f, 0xf9, 0xff, - 0x04, 0xfa, 0xff, 0x1b, 0x24, 0x0b, 0xf9, 0x00, - 0x03, 0xf9, 0x01, 0x1f, 0x23, 0x08, 0xf8, 0x01, - 0x02, 0xf9, 0x04, 0x22, 0x20, 0x04, 0xf9, 0x02, - 0x01, 0xf8, 0x08, 0x25, 0x1d, 0x01, 0xf9, 0x03, - 0x00, 0xf9, 0x0c, 0x25, 0x1a, 0xfe, 0xfa, 0x04, - 0xff, 0xf9, 0x10, 0x26, 0x15, 0xfc, 0xfc, 0x05 - } - }, - { - .min = 2048, - .max = 3072, - .coef = { - 0xfc, 0xfd, 0x06, 0x13, 0x18, 0x13, 0x06, 0xfd, - 0xfc, 0xfe, 0x08, 0x15, 0x17, 0x12, 0x04, 0xfc, - 0xfb, 0xfe, 0x0a, 0x16, 0x18, 0x10, 0x03, 0xfc, - 0xfb, 0x00, 0x0b, 0x18, 0x17, 0x0f, 0x01, 0xfb, - 0xfb, 0x00, 0x0d, 0x19, 0x17, 0x0d, 0x00, 0xfb, - 0xfb, 0x01, 0x0f, 0x19, 0x16, 0x0b, 0x00, 0xfb, - 0xfc, 0x03, 0x11, 0x19, 0x15, 0x09, 0xfe, 0xfb, - 0xfc, 0x04, 0x12, 0x1a, 0x12, 0x08, 0xfe, 0xfc - } - }, - { - .min = 3072, - .max = 4096, - .coef = { - 0xfe, 0x02, 0x09, 0x0f, 0x0e, 0x0f, 0x09, 0x02, - 0xff, 0x02, 0x09, 0x0f, 0x10, 0x0e, 0x08, 0x01, - 0xff, 0x03, 0x0a, 0x10, 0x10, 0x0d, 0x07, 0x00, - 0x00, 0x04, 0x0b, 0x10, 0x0f, 0x0c, 0x06, 0x00, - 0x00, 0x05, 0x0c, 0x10, 0x0e, 0x0c, 0x05, 0x00, - 0x00, 0x06, 0x0c, 0x11, 0x0e, 0x0b, 0x04, 0x00, - 0x00, 0x07, 0x0d, 0x11, 0x0f, 0x0a, 0x03, 0xff, - 0x01, 0x08, 0x0e, 0x11, 0x0e, 0x09, 0x02, 0xff - } - }, - { - .min = 4096, - .max = 5120, - .coef = { - 0x00, 0x04, 0x09, 0x0c, 0x0e, 0x0c, 0x09, 0x04, - 0x01, 0x05, 0x09, 0x0c, 0x0d, 0x0c, 0x08, 0x04, - 0x01, 0x05, 0x0a, 0x0c, 0x0e, 0x0b, 0x08, 0x03, - 0x02, 0x06, 0x0a, 0x0d, 0x0c, 0x0b, 0x07, 0x03, - 0x02, 0x07, 0x0a, 0x0d, 0x0d, 0x0a, 0x07, 0x02, - 0x03, 0x07, 0x0b, 0x0d, 0x0c, 0x0a, 0x06, 0x02, - 0x03, 0x08, 0x0b, 0x0d, 0x0d, 0x0a, 0x05, 0x01, - 0x04, 0x08, 0x0c, 0x0d, 0x0c, 0x09, 0x05, 0x01 - } - }, - { - .min = 5120, - .max = 65535, - .coef = { - 0x03, 0x06, 0x09, 0x0b, 0x09, 0x0b, 0x09, 0x06, - 0x03, 0x06, 0x09, 0x0b, 0x0c, 0x0a, 0x08, 0x05, - 0x03, 0x06, 0x09, 0x0b, 0x0c, 0x0a, 0x08, 0x05, - 0x04, 0x07, 0x09, 0x0b, 0x0b, 0x0a, 0x08, 0x04, - 0x04, 0x07, 0x0a, 0x0b, 0x0b, 0x0a, 0x07, 0x04, - 0x04, 0x08, 0x0a, 0x0b, 0x0b, 0x09, 0x07, 0x04, - 0x05, 0x08, 0x0a, 0x0b, 0x0c, 0x09, 0x06, 0x03, - 0x05, 0x08, 0x0a, 0x0b, 0x0c, 0x09, 0x06, 0x03 - } - } -}; - /** * struct bdisp_filter_v_spec - Vertical filter specification * @@ -204,138 +32,6 @@ struct bdisp_filter_v_spec { const u8 coef[BDISP_VF_NB]; }; -static const struct bdisp_filter_v_spec bdisp_v_spec[] = { - { - .min = 0, - .max = 1024, - .coef = { - 0x00, 0x00, 0x40, 0x00, 0x00, - 0x00, 0x06, 0x3d, 0xfd, 0x00, - 0xfe, 0x0f, 0x38, 0xfb, 0x00, - 0xfd, 0x19, 0x2f, 0xfb, 0x00, - 0xfc, 0x24, 0x24, 0xfc, 0x00, - 0xfb, 0x2f, 0x19, 0xfd, 0x00, - 0xfb, 0x38, 0x0f, 0xfe, 0x00, - 0xfd, 0x3d, 0x06, 0x00, 0x00 - } - }, - { - .min = 1024, - .max = 1331, - .coef = { - 0xfc, 0x05, 0x3e, 0x05, 0xfc, - 0xf8, 0x0e, 0x3b, 0xff, 0x00, - 0xf5, 0x18, 0x38, 0xf9, 0x02, - 0xf4, 0x21, 0x31, 0xf5, 0x05, - 0xf4, 0x2a, 0x27, 0xf4, 0x07, - 0xf6, 0x30, 0x1e, 0xf4, 0x08, - 0xf9, 0x35, 0x15, 0xf6, 0x07, - 0xff, 0x37, 0x0b, 0xf9, 0x06 - } - }, - { - .min = 1331, - .max = 1433, - .coef = { - 0xf8, 0x0a, 0x3c, 0x0a, 0xf8, - 0xf6, 0x12, 0x3b, 0x02, 0xfb, - 0xf4, 0x1b, 0x35, 0xfd, 0xff, - 0xf4, 0x23, 0x30, 0xf8, 0x01, - 0xf6, 0x29, 0x27, 0xf6, 0x04, - 0xf9, 0x2e, 0x1e, 0xf5, 0x06, - 0xfd, 0x31, 0x16, 0xf6, 0x06, - 0x02, 0x32, 0x0d, 0xf8, 0x07 - } - }, - { - .min = 1433, - .max = 1536, - .coef = { - 0xf6, 0x0e, 0x38, 0x0e, 0xf6, - 0xf5, 0x15, 0x38, 0x06, 0xf8, - 0xf5, 0x1d, 0x33, 0x00, 0xfb, - 0xf6, 0x23, 0x2d, 0xfc, 0xfe, - 0xf9, 0x28, 0x26, 0xf9, 0x00, - 0xfc, 0x2c, 0x1e, 0xf7, 0x03, - 0x00, 0x2e, 0x18, 0xf6, 0x04, - 0x05, 0x2e, 0x11, 0xf7, 0x05 - } - }, - { - .min = 1536, - .max = 2048, - .coef = { - 0xfb, 0x13, 0x24, 0x13, 0xfb, - 0xfd, 0x17, 0x23, 0x0f, 0xfa, - 0xff, 0x1a, 0x23, 0x0b, 0xf9, - 0x01, 0x1d, 0x22, 0x07, 0xf9, - 0x04, 0x20, 0x1f, 0x04, 0xf9, - 0x07, 0x22, 0x1c, 0x01, 0xfa, - 0x0b, 0x24, 0x17, 0xff, 0xfb, - 0x0f, 0x24, 0x14, 0xfd, 0xfc - } - }, - { - .min = 2048, - .max = 3072, - .coef = { - 0x05, 0x10, 0x16, 0x10, 0x05, - 0x06, 0x11, 0x16, 0x0f, 0x04, - 0x08, 0x13, 0x15, 0x0e, 0x02, - 0x09, 0x14, 0x16, 0x0c, 0x01, - 0x0b, 0x15, 0x15, 0x0b, 0x00, - 0x0d, 0x16, 0x13, 0x0a, 0x00, - 0x0f, 0x17, 0x13, 0x08, 0xff, - 0x11, 0x18, 0x12, 0x07, 0xfe - } - }, - { - .min = 3072, - .max = 4096, - .coef = { - 0x09, 0x0f, 0x10, 0x0f, 0x09, - 0x09, 0x0f, 0x12, 0x0e, 0x08, - 0x0a, 0x10, 0x11, 0x0e, 0x07, - 0x0b, 0x11, 0x11, 0x0d, 0x06, - 0x0c, 0x11, 0x12, 0x0c, 0x05, - 0x0d, 0x12, 0x11, 0x0c, 0x04, - 0x0e, 0x12, 0x11, 0x0b, 0x04, - 0x0f, 0x13, 0x11, 0x0a, 0x03 - } - }, - { - .min = 4096, - .max = 5120, - .coef = { - 0x0a, 0x0e, 0x10, 0x0e, 0x0a, - 0x0b, 0x0e, 0x0f, 0x0e, 0x0a, - 0x0b, 0x0f, 0x10, 0x0d, 0x09, - 0x0c, 0x0f, 0x10, 0x0d, 0x08, - 0x0d, 0x0f, 0x0f, 0x0d, 0x08, - 0x0d, 0x10, 0x10, 0x0c, 0x07, - 0x0e, 0x10, 0x0f, 0x0c, 0x07, - 0x0f, 0x10, 0x10, 0x0b, 0x06 - } - }, - { - .min = 5120, - .max = 65535, - .coef = { - 0x0b, 0x0e, 0x0e, 0x0e, 0x0b, - 0x0b, 0x0e, 0x0f, 0x0d, 0x0b, - 0x0c, 0x0e, 0x0f, 0x0d, 0x0a, - 0x0c, 0x0e, 0x0f, 0x0d, 0x0a, - 0x0d, 0x0f, 0x0e, 0x0d, 0x09, - 0x0d, 0x0f, 0x0f, 0x0c, 0x09, - 0x0e, 0x0f, 0x0e, 0x0c, 0x09, - 0x0e, 0x0f, 0x0f, 0x0c, 0x08 - } - } -}; - -#define NB_H_FILTER ARRAY_SIZE(bdisp_h_spec) -#define NB_V_FILTER ARRAY_SIZE(bdisp_v_spec) - /* RGB YUV 601 standard conversion */ static const u32 bdisp_rgb_to_yuv[] = { 0x0e1e8bee, 0x08420419, 0xfb5ed471, 0x08004080, diff --git a/drivers/media/platform/sti/bdisp/bdisp-hw.c b/drivers/media/platform/sti/bdisp/bdisp-hw.c index 052c932ac942..3df66d11c795 100644 --- a/drivers/media/platform/sti/bdisp/bdisp-hw.c +++ b/drivers/media/platform/sti/bdisp/bdisp-hw.c @@ -47,6 +47,311 @@ struct bdisp_filter_addr { dma_addr_t paddr; /* Physical address for filter table */ }; +static const struct bdisp_filter_h_spec bdisp_h_spec[] = { + { + .min = 0, + .max = 921, + .coef = { + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0x07, 0x3d, 0xfc, 0x01, 0x00, + 0x00, 0x01, 0xfd, 0x11, 0x36, 0xf9, 0x02, 0x00, + 0x00, 0x01, 0xfb, 0x1b, 0x2e, 0xf9, 0x02, 0x00, + 0x00, 0x01, 0xf9, 0x26, 0x26, 0xf9, 0x01, 0x00, + 0x00, 0x02, 0xf9, 0x30, 0x19, 0xfb, 0x01, 0x00, + 0x00, 0x02, 0xf9, 0x39, 0x0e, 0xfd, 0x01, 0x00, + 0x00, 0x01, 0xfc, 0x3e, 0x06, 0xff, 0x00, 0x00 + } + }, + { + .min = 921, + .max = 1024, + .coef = { + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0xff, 0x03, 0xfd, 0x08, 0x3e, 0xf9, 0x04, 0xfe, + 0xfd, 0x06, 0xf8, 0x13, 0x3b, 0xf4, 0x07, 0xfc, + 0xfb, 0x08, 0xf5, 0x1f, 0x34, 0xf1, 0x09, 0xfb, + 0xfb, 0x09, 0xf2, 0x2b, 0x2a, 0xf1, 0x09, 0xfb, + 0xfb, 0x09, 0xf2, 0x35, 0x1e, 0xf4, 0x08, 0xfb, + 0xfc, 0x07, 0xf5, 0x3c, 0x12, 0xf7, 0x06, 0xfd, + 0xfe, 0x04, 0xfa, 0x3f, 0x07, 0xfc, 0x03, 0xff + } + }, + { + .min = 1024, + .max = 1126, + .coef = { + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0xff, 0x03, 0xfd, 0x08, 0x3e, 0xf9, 0x04, 0xfe, + 0xfd, 0x06, 0xf8, 0x13, 0x3b, 0xf4, 0x07, 0xfc, + 0xfb, 0x08, 0xf5, 0x1f, 0x34, 0xf1, 0x09, 0xfb, + 0xfb, 0x09, 0xf2, 0x2b, 0x2a, 0xf1, 0x09, 0xfb, + 0xfb, 0x09, 0xf2, 0x35, 0x1e, 0xf4, 0x08, 0xfb, + 0xfc, 0x07, 0xf5, 0x3c, 0x12, 0xf7, 0x06, 0xfd, + 0xfe, 0x04, 0xfa, 0x3f, 0x07, 0xfc, 0x03, 0xff + } + }, + { + .min = 1126, + .max = 1228, + .coef = { + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0xff, 0x03, 0xfd, 0x08, 0x3e, 0xf9, 0x04, 0xfe, + 0xfd, 0x06, 0xf8, 0x13, 0x3b, 0xf4, 0x07, 0xfc, + 0xfb, 0x08, 0xf5, 0x1f, 0x34, 0xf1, 0x09, 0xfb, + 0xfb, 0x09, 0xf2, 0x2b, 0x2a, 0xf1, 0x09, 0xfb, + 0xfb, 0x09, 0xf2, 0x35, 0x1e, 0xf4, 0x08, 0xfb, + 0xfc, 0x07, 0xf5, 0x3c, 0x12, 0xf7, 0x06, 0xfd, + 0xfe, 0x04, 0xfa, 0x3f, 0x07, 0xfc, 0x03, 0xff + } + }, + { + .min = 1228, + .max = 1331, + .coef = { + 0xfd, 0x04, 0xfc, 0x05, 0x39, 0x05, 0xfc, 0x04, + 0xfc, 0x06, 0xf9, 0x0c, 0x39, 0xfe, 0x00, 0x02, + 0xfb, 0x08, 0xf6, 0x17, 0x35, 0xf9, 0x02, 0x00, + 0xfc, 0x08, 0xf4, 0x20, 0x30, 0xf4, 0x05, 0xff, + 0xfd, 0x07, 0xf4, 0x29, 0x28, 0xf3, 0x07, 0xfd, + 0xff, 0x05, 0xf5, 0x31, 0x1f, 0xf3, 0x08, 0xfc, + 0x00, 0x02, 0xf9, 0x38, 0x14, 0xf6, 0x08, 0xfb, + 0x02, 0x00, 0xff, 0x3a, 0x0b, 0xf8, 0x06, 0xfc + } + }, + { + .min = 1331, + .max = 1433, + .coef = { + 0xfc, 0x06, 0xf9, 0x09, 0x34, 0x09, 0xf9, 0x06, + 0xfd, 0x07, 0xf7, 0x10, 0x32, 0x02, 0xfc, 0x05, + 0xfe, 0x07, 0xf6, 0x17, 0x2f, 0xfc, 0xff, 0x04, + 0xff, 0x06, 0xf5, 0x20, 0x2a, 0xf9, 0x01, 0x02, + 0x00, 0x04, 0xf6, 0x27, 0x25, 0xf6, 0x04, 0x00, + 0x02, 0x01, 0xf9, 0x2d, 0x1d, 0xf5, 0x06, 0xff, + 0x04, 0xff, 0xfd, 0x31, 0x15, 0xf5, 0x07, 0xfe, + 0x05, 0xfc, 0x02, 0x35, 0x0d, 0xf7, 0x07, 0xfd + } + }, + { + .min = 1433, + .max = 1536, + .coef = { + 0xfe, 0x06, 0xf8, 0x0b, 0x30, 0x0b, 0xf8, 0x06, + 0xff, 0x06, 0xf7, 0x12, 0x2d, 0x05, 0xfa, 0x06, + 0x00, 0x04, 0xf6, 0x18, 0x2c, 0x00, 0xfc, 0x06, + 0x01, 0x02, 0xf7, 0x1f, 0x27, 0xfd, 0xff, 0x04, + 0x03, 0x00, 0xf9, 0x24, 0x24, 0xf9, 0x00, 0x03, + 0x04, 0xff, 0xfd, 0x29, 0x1d, 0xf7, 0x02, 0x01, + 0x06, 0xfc, 0x00, 0x2d, 0x17, 0xf6, 0x04, 0x00, + 0x06, 0xfa, 0x05, 0x30, 0x0f, 0xf7, 0x06, 0xff + } + }, + { + .min = 1536, + .max = 2048, + .coef = { + 0x05, 0xfd, 0xfb, 0x13, 0x25, 0x13, 0xfb, 0xfd, + 0x05, 0xfc, 0xfd, 0x17, 0x24, 0x0f, 0xf9, 0xff, + 0x04, 0xfa, 0xff, 0x1b, 0x24, 0x0b, 0xf9, 0x00, + 0x03, 0xf9, 0x01, 0x1f, 0x23, 0x08, 0xf8, 0x01, + 0x02, 0xf9, 0x04, 0x22, 0x20, 0x04, 0xf9, 0x02, + 0x01, 0xf8, 0x08, 0x25, 0x1d, 0x01, 0xf9, 0x03, + 0x00, 0xf9, 0x0c, 0x25, 0x1a, 0xfe, 0xfa, 0x04, + 0xff, 0xf9, 0x10, 0x26, 0x15, 0xfc, 0xfc, 0x05 + } + }, + { + .min = 2048, + .max = 3072, + .coef = { + 0xfc, 0xfd, 0x06, 0x13, 0x18, 0x13, 0x06, 0xfd, + 0xfc, 0xfe, 0x08, 0x15, 0x17, 0x12, 0x04, 0xfc, + 0xfb, 0xfe, 0x0a, 0x16, 0x18, 0x10, 0x03, 0xfc, + 0xfb, 0x00, 0x0b, 0x18, 0x17, 0x0f, 0x01, 0xfb, + 0xfb, 0x00, 0x0d, 0x19, 0x17, 0x0d, 0x00, 0xfb, + 0xfb, 0x01, 0x0f, 0x19, 0x16, 0x0b, 0x00, 0xfb, + 0xfc, 0x03, 0x11, 0x19, 0x15, 0x09, 0xfe, 0xfb, + 0xfc, 0x04, 0x12, 0x1a, 0x12, 0x08, 0xfe, 0xfc + } + }, + { + .min = 3072, + .max = 4096, + .coef = { + 0xfe, 0x02, 0x09, 0x0f, 0x0e, 0x0f, 0x09, 0x02, + 0xff, 0x02, 0x09, 0x0f, 0x10, 0x0e, 0x08, 0x01, + 0xff, 0x03, 0x0a, 0x10, 0x10, 0x0d, 0x07, 0x00, + 0x00, 0x04, 0x0b, 0x10, 0x0f, 0x0c, 0x06, 0x00, + 0x00, 0x05, 0x0c, 0x10, 0x0e, 0x0c, 0x05, 0x00, + 0x00, 0x06, 0x0c, 0x11, 0x0e, 0x0b, 0x04, 0x00, + 0x00, 0x07, 0x0d, 0x11, 0x0f, 0x0a, 0x03, 0xff, + 0x01, 0x08, 0x0e, 0x11, 0x0e, 0x09, 0x02, 0xff + } + }, + { + .min = 4096, + .max = 5120, + .coef = { + 0x00, 0x04, 0x09, 0x0c, 0x0e, 0x0c, 0x09, 0x04, + 0x01, 0x05, 0x09, 0x0c, 0x0d, 0x0c, 0x08, 0x04, + 0x01, 0x05, 0x0a, 0x0c, 0x0e, 0x0b, 0x08, 0x03, + 0x02, 0x06, 0x0a, 0x0d, 0x0c, 0x0b, 0x07, 0x03, + 0x02, 0x07, 0x0a, 0x0d, 0x0d, 0x0a, 0x07, 0x02, + 0x03, 0x07, 0x0b, 0x0d, 0x0c, 0x0a, 0x06, 0x02, + 0x03, 0x08, 0x0b, 0x0d, 0x0d, 0x0a, 0x05, 0x01, + 0x04, 0x08, 0x0c, 0x0d, 0x0c, 0x09, 0x05, 0x01 + } + }, + { + .min = 5120, + .max = 65535, + .coef = { + 0x03, 0x06, 0x09, 0x0b, 0x09, 0x0b, 0x09, 0x06, + 0x03, 0x06, 0x09, 0x0b, 0x0c, 0x0a, 0x08, 0x05, + 0x03, 0x06, 0x09, 0x0b, 0x0c, 0x0a, 0x08, 0x05, + 0x04, 0x07, 0x09, 0x0b, 0x0b, 0x0a, 0x08, 0x04, + 0x04, 0x07, 0x0a, 0x0b, 0x0b, 0x0a, 0x07, 0x04, + 0x04, 0x08, 0x0a, 0x0b, 0x0b, 0x09, 0x07, 0x04, + 0x05, 0x08, 0x0a, 0x0b, 0x0c, 0x09, 0x06, 0x03, + 0x05, 0x08, 0x0a, 0x0b, 0x0c, 0x09, 0x06, 0x03 + } + } +}; + +#define NB_H_FILTER ARRAY_SIZE(bdisp_h_spec) + + +static const struct bdisp_filter_v_spec bdisp_v_spec[] = { + { + .min = 0, + .max = 1024, + .coef = { + 0x00, 0x00, 0x40, 0x00, 0x00, + 0x00, 0x06, 0x3d, 0xfd, 0x00, + 0xfe, 0x0f, 0x38, 0xfb, 0x00, + 0xfd, 0x19, 0x2f, 0xfb, 0x00, + 0xfc, 0x24, 0x24, 0xfc, 0x00, + 0xfb, 0x2f, 0x19, 0xfd, 0x00, + 0xfb, 0x38, 0x0f, 0xfe, 0x00, + 0xfd, 0x3d, 0x06, 0x00, 0x00 + } + }, + { + .min = 1024, + .max = 1331, + .coef = { + 0xfc, 0x05, 0x3e, 0x05, 0xfc, + 0xf8, 0x0e, 0x3b, 0xff, 0x00, + 0xf5, 0x18, 0x38, 0xf9, 0x02, + 0xf4, 0x21, 0x31, 0xf5, 0x05, + 0xf4, 0x2a, 0x27, 0xf4, 0x07, + 0xf6, 0x30, 0x1e, 0xf4, 0x08, + 0xf9, 0x35, 0x15, 0xf6, 0x07, + 0xff, 0x37, 0x0b, 0xf9, 0x06 + } + }, + { + .min = 1331, + .max = 1433, + .coef = { + 0xf8, 0x0a, 0x3c, 0x0a, 0xf8, + 0xf6, 0x12, 0x3b, 0x02, 0xfb, + 0xf4, 0x1b, 0x35, 0xfd, 0xff, + 0xf4, 0x23, 0x30, 0xf8, 0x01, + 0xf6, 0x29, 0x27, 0xf6, 0x04, + 0xf9, 0x2e, 0x1e, 0xf5, 0x06, + 0xfd, 0x31, 0x16, 0xf6, 0x06, + 0x02, 0x32, 0x0d, 0xf8, 0x07 + } + }, + { + .min = 1433, + .max = 1536, + .coef = { + 0xf6, 0x0e, 0x38, 0x0e, 0xf6, + 0xf5, 0x15, 0x38, 0x06, 0xf8, + 0xf5, 0x1d, 0x33, 0x00, 0xfb, + 0xf6, 0x23, 0x2d, 0xfc, 0xfe, + 0xf9, 0x28, 0x26, 0xf9, 0x00, + 0xfc, 0x2c, 0x1e, 0xf7, 0x03, + 0x00, 0x2e, 0x18, 0xf6, 0x04, + 0x05, 0x2e, 0x11, 0xf7, 0x05 + } + }, + { + .min = 1536, + .max = 2048, + .coef = { + 0xfb, 0x13, 0x24, 0x13, 0xfb, + 0xfd, 0x17, 0x23, 0x0f, 0xfa, + 0xff, 0x1a, 0x23, 0x0b, 0xf9, + 0x01, 0x1d, 0x22, 0x07, 0xf9, + 0x04, 0x20, 0x1f, 0x04, 0xf9, + 0x07, 0x22, 0x1c, 0x01, 0xfa, + 0x0b, 0x24, 0x17, 0xff, 0xfb, + 0x0f, 0x24, 0x14, 0xfd, 0xfc + } + }, + { + .min = 2048, + .max = 3072, + .coef = { + 0x05, 0x10, 0x16, 0x10, 0x05, + 0x06, 0x11, 0x16, 0x0f, 0x04, + 0x08, 0x13, 0x15, 0x0e, 0x02, + 0x09, 0x14, 0x16, 0x0c, 0x01, + 0x0b, 0x15, 0x15, 0x0b, 0x00, + 0x0d, 0x16, 0x13, 0x0a, 0x00, + 0x0f, 0x17, 0x13, 0x08, 0xff, + 0x11, 0x18, 0x12, 0x07, 0xfe + } + }, + { + .min = 3072, + .max = 4096, + .coef = { + 0x09, 0x0f, 0x10, 0x0f, 0x09, + 0x09, 0x0f, 0x12, 0x0e, 0x08, + 0x0a, 0x10, 0x11, 0x0e, 0x07, + 0x0b, 0x11, 0x11, 0x0d, 0x06, + 0x0c, 0x11, 0x12, 0x0c, 0x05, + 0x0d, 0x12, 0x11, 0x0c, 0x04, + 0x0e, 0x12, 0x11, 0x0b, 0x04, + 0x0f, 0x13, 0x11, 0x0a, 0x03 + } + }, + { + .min = 4096, + .max = 5120, + .coef = { + 0x0a, 0x0e, 0x10, 0x0e, 0x0a, + 0x0b, 0x0e, 0x0f, 0x0e, 0x0a, + 0x0b, 0x0f, 0x10, 0x0d, 0x09, + 0x0c, 0x0f, 0x10, 0x0d, 0x08, + 0x0d, 0x0f, 0x0f, 0x0d, 0x08, + 0x0d, 0x10, 0x10, 0x0c, 0x07, + 0x0e, 0x10, 0x0f, 0x0c, 0x07, + 0x0f, 0x10, 0x10, 0x0b, 0x06 + } + }, + { + .min = 5120, + .max = 65535, + .coef = { + 0x0b, 0x0e, 0x0e, 0x0e, 0x0b, + 0x0b, 0x0e, 0x0f, 0x0d, 0x0b, + 0x0c, 0x0e, 0x0f, 0x0d, 0x0a, + 0x0c, 0x0e, 0x0f, 0x0d, 0x0a, + 0x0d, 0x0f, 0x0e, 0x0d, 0x09, + 0x0d, 0x0f, 0x0f, 0x0c, 0x09, + 0x0e, 0x0f, 0x0e, 0x0c, 0x09, + 0x0e, 0x0f, 0x0f, 0x0c, 0x08 + } + } +}; + +#define NB_V_FILTER ARRAY_SIZE(bdisp_v_spec) + static struct bdisp_filter_addr bdisp_h_filter[NB_H_FILTER]; static struct bdisp_filter_addr bdisp_v_filter[NB_V_FILTER]; diff --git a/drivers/media/platform/vivid/Kconfig b/drivers/media/platform/vivid/Kconfig index f535f576913d..8e6918c5c87c 100644 --- a/drivers/media/platform/vivid/Kconfig +++ b/drivers/media/platform/vivid/Kconfig @@ -6,6 +6,7 @@ config VIDEO_VIVID select FB_CFB_FILLRECT select FB_CFB_COPYAREA select FB_CFB_IMAGEBLIT + select MEDIA_CEC_EDID select VIDEOBUF2_VMALLOC select VIDEO_V4L2_TPG default n @@ -22,6 +23,13 @@ config VIDEO_VIVID Say Y here if you want to test video apps or debug V4L devices. When in doubt, say N. +config VIDEO_VIVID_CEC + bool "Enable CEC emulation support" + depends on VIDEO_VIVID && MEDIA_CEC + ---help--- + When selected the vivid module will emulate the optional + HDMI CEC feature. + config VIDEO_VIVID_MAX_DEVS int "Maximum number of devices" depends on VIDEO_VIVID diff --git a/drivers/media/platform/vivid/Makefile b/drivers/media/platform/vivid/Makefile index 633c8a1b2c27..29738810e3ee 100644 --- a/drivers/media/platform/vivid/Makefile +++ b/drivers/media/platform/vivid/Makefile @@ -3,4 +3,8 @@ vivid-objs := vivid-core.o vivid-ctrls.o vivid-vid-common.o vivid-vbi-gen.o \ vivid-radio-rx.o vivid-radio-tx.o vivid-radio-common.o \ vivid-rds-gen.o vivid-sdr-cap.o vivid-vbi-cap.o vivid-vbi-out.o \ vivid-osd.o +ifeq ($(CONFIG_VIDEO_VIVID_CEC),y) + vivid-objs += vivid-cec.o +endif + obj-$(CONFIG_VIDEO_VIVID) += vivid.o diff --git a/drivers/media/platform/vivid/vivid-cec.c b/drivers/media/platform/vivid/vivid-cec.c new file mode 100644 index 000000000000..b5714fae905d --- /dev/null +++ b/drivers/media/platform/vivid/vivid-cec.c @@ -0,0 +1,255 @@ +/* + * vivid-cec.c - A Virtual Video Test Driver, cec emulation + * + * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * This program is free software; you may redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include <media/cec.h> + +#include "vivid-core.h" +#include "vivid-cec.h" + +void vivid_cec_bus_free_work(struct vivid_dev *dev) +{ + spin_lock(&dev->cec_slock); + while (!list_empty(&dev->cec_work_list)) { + struct vivid_cec_work *cw = + list_first_entry(&dev->cec_work_list, + struct vivid_cec_work, list); + + spin_unlock(&dev->cec_slock); + 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); + kfree(cw); + } + spin_unlock(&dev->cec_slock); +} + +static struct cec_adapter *vivid_cec_find_dest_adap(struct vivid_dev *dev, + struct cec_adapter *adap, + u8 dest) +{ + unsigned int i; + + if (dest >= 0xf) + return NULL; + + if (adap != dev->cec_rx_adap && dev->cec_rx_adap && + dev->cec_rx_adap->is_configured && + cec_has_log_addr(dev->cec_rx_adap, dest)) + return dev->cec_rx_adap; + + for (i = 0; i < MAX_OUTPUTS && dev->cec_tx_adap[i]; i++) { + if (adap == dev->cec_tx_adap[i]) + continue; + if (!dev->cec_tx_adap[i]->is_configured) + continue; + if (cec_has_log_addr(dev->cec_tx_adap[i], dest)) + return dev->cec_tx_adap[i]; + } + return NULL; +} + +static void vivid_cec_xfer_done_worker(struct work_struct *work) +{ + struct vivid_cec_work *cw = + container_of(work, struct vivid_cec_work, work.work); + struct vivid_dev *dev = cw->dev; + struct cec_adapter *adap = cw->adap; + bool is_poll = cw->msg.len == 1; + u8 dest = cec_msg_destination(&cw->msg); + struct cec_adapter *dest_adap = NULL; + bool valid_dest; + unsigned int i; + + valid_dest = cec_msg_is_broadcast(&cw->msg); + if (!valid_dest) { + dest_adap = vivid_cec_find_dest_adap(dev, adap, dest); + if (dest_adap) + valid_dest = true; + } + cw->tx_status = valid_dest ? CEC_TX_STATUS_OK : CEC_TX_STATUS_NACK; + spin_lock(&dev->cec_slock); + dev->cec_xfer_time_jiffies = 0; + 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); + + if (!is_poll && dest_adap) { + /* Directed message */ + cec_received_msg(dest_adap, &cw->msg); + } else if (!is_poll && valid_dest) { + /* Broadcast message */ + if (adap != dev->cec_rx_adap && + dev->cec_rx_adap->log_addrs.log_addr_mask) + cec_received_msg(dev->cec_rx_adap, &cw->msg); + for (i = 0; i < MAX_OUTPUTS && dev->cec_tx_adap[i]; i++) { + if (adap == dev->cec_tx_adap[i] || + !dev->cec_tx_adap[i]->log_addrs.log_addr_mask) + continue; + cec_received_msg(dev->cec_tx_adap[i], &cw->msg); + } + } + kfree(cw); +} + +static void vivid_cec_xfer_try_worker(struct work_struct *work) +{ + struct vivid_cec_work *cw = + container_of(work, struct vivid_cec_work, work.work); + struct vivid_dev *dev = cw->dev; + + spin_lock(&dev->cec_slock); + 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); + kfree(cw); + } else { + INIT_DELAYED_WORK(&cw->work, vivid_cec_xfer_done_worker); + dev->cec_xfer_start_jiffies = jiffies; + dev->cec_xfer_time_jiffies = usecs_to_jiffies(cw->usecs); + spin_unlock(&dev->cec_slock); + schedule_delayed_work(&cw->work, dev->cec_xfer_time_jiffies); + } +} + +static int vivid_cec_adap_enable(struct cec_adapter *adap, bool enable) +{ + return 0; +} + +static int vivid_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr) +{ + return 0; +} + +/* + * One data bit takes 2400 us, each byte needs 10 bits so that's 24000 us + * per byte. + */ +#define USECS_PER_BYTE 24000 + +static int vivid_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, + u32 signal_free_time, struct cec_msg *msg) +{ + struct vivid_dev *dev = adap->priv; + struct vivid_cec_work *cw = kzalloc(sizeof(*cw), GFP_KERNEL); + long delta_jiffies = 0; + + if (cw == NULL) + return -ENOMEM; + cw->dev = dev; + cw->adap = adap; + cw->usecs = CEC_FREE_TIME_TO_USEC(signal_free_time) + + msg->len * USECS_PER_BYTE; + cw->msg = *msg; + + spin_lock(&dev->cec_slock); + list_add(&cw->list, &dev->cec_work_list); + if (dev->cec_xfer_time_jiffies == 0) { + INIT_DELAYED_WORK(&cw->work, vivid_cec_xfer_done_worker); + dev->cec_xfer_start_jiffies = jiffies; + dev->cec_xfer_time_jiffies = usecs_to_jiffies(cw->usecs); + delta_jiffies = dev->cec_xfer_time_jiffies; + } else { + INIT_DELAYED_WORK(&cw->work, vivid_cec_xfer_try_worker); + delta_jiffies = dev->cec_xfer_start_jiffies + + dev->cec_xfer_time_jiffies - jiffies; + } + spin_unlock(&dev->cec_slock); + schedule_delayed_work(&cw->work, delta_jiffies < 0 ? 0 : delta_jiffies); + return 0; +} + +static int vivid_received(struct cec_adapter *adap, struct cec_msg *msg) +{ + struct vivid_dev *dev = adap->priv; + struct cec_msg reply; + u8 dest = cec_msg_destination(msg); + u16 pa; + u8 disp_ctl; + char osd[14]; + + if (cec_msg_is_broadcast(msg)) + dest = adap->log_addrs.log_addr[0]; + cec_msg_init(&reply, dest, cec_msg_initiator(msg)); + + switch (cec_msg_opcode(msg)) { + case CEC_MSG_SET_STREAM_PATH: + if (cec_is_sink(adap)) + return -ENOMSG; + cec_ops_set_stream_path(msg, &pa); + if (pa != adap->phys_addr) + return -ENOMSG; + cec_msg_active_source(&reply, adap->phys_addr); + cec_transmit_msg(adap, &reply, false); + break; + case CEC_MSG_SET_OSD_STRING: + if (!cec_is_sink(adap)) + return -ENOMSG; + cec_ops_set_osd_string(msg, &disp_ctl, osd); + switch (disp_ctl) { + case CEC_OP_DISP_CTL_DEFAULT: + strcpy(dev->osd, osd); + dev->osd_jiffies = jiffies; + break; + case CEC_OP_DISP_CTL_UNTIL_CLEARED: + strcpy(dev->osd, osd); + dev->osd_jiffies = 0; + break; + case CEC_OP_DISP_CTL_CLEAR: + dev->osd[0] = 0; + dev->osd_jiffies = 0; + break; + default: + cec_msg_feature_abort(&reply, cec_msg_opcode(msg), + CEC_OP_ABORT_INVALID_OP); + cec_transmit_msg(adap, &reply, false); + break; + } + break; + default: + return -ENOMSG; + } + return 0; +} + +static const struct cec_adap_ops vivid_cec_adap_ops = { + .adap_enable = vivid_cec_adap_enable, + .adap_log_addr = vivid_cec_adap_log_addr, + .adap_transmit = vivid_cec_adap_transmit, + .received = vivid_received, +}; + +struct cec_adapter *vivid_cec_alloc_adap(struct vivid_dev *dev, + unsigned int idx, + struct device *parent, + bool is_source) +{ + char name[sizeof(dev->vid_out_dev.name) + 2]; + u32 caps = CEC_CAP_TRANSMIT | CEC_CAP_LOG_ADDRS | + CEC_CAP_PASSTHROUGH | CEC_CAP_RC; + + snprintf(name, sizeof(name), "%s%d", + is_source ? dev->vid_out_dev.name : dev->vid_cap_dev.name, + idx); + return cec_allocate_adapter(&vivid_cec_adap_ops, dev, + name, caps, 1, parent); +} diff --git a/drivers/media/platform/vivid/vivid-cec.h b/drivers/media/platform/vivid/vivid-cec.h new file mode 100644 index 000000000000..97892afa6b3b --- /dev/null +++ b/drivers/media/platform/vivid/vivid-cec.h @@ -0,0 +1,33 @@ +/* + * vivid-cec.h - A Virtual Video Test Driver, cec emulation + * + * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * This program is free software; you may redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifdef CONFIG_VIDEO_VIVID_CEC +struct cec_adapter *vivid_cec_alloc_adap(struct vivid_dev *dev, + unsigned int idx, + struct device *parent, + bool is_source); +void vivid_cec_bus_free_work(struct vivid_dev *dev); + +#else + +static inline void vivid_cec_bus_free_work(struct vivid_dev *dev) +{ +} + +#endif diff --git a/drivers/media/platform/vivid/vivid-core.c b/drivers/media/platform/vivid/vivid-core.c index c14da84af09b..9966828bb578 100644 --- a/drivers/media/platform/vivid/vivid-core.c +++ b/drivers/media/platform/vivid/vivid-core.c @@ -46,6 +46,7 @@ #include "vivid-vbi-cap.h" #include "vivid-vbi-out.h" #include "vivid-osd.h" +#include "vivid-cec.h" #include "vivid-ctrls.h" #define VIVID_MODULE_NAME "vivid" @@ -684,6 +685,11 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) dev->input_name_counter[i] = in_type_counter[dev->input_type[i]]++; } dev->has_audio_inputs = in_type_counter[TV] && in_type_counter[SVID]; + if (in_type_counter[HDMI] == 16) { + /* The CEC physical address only allows for max 15 inputs */ + in_type_counter[HDMI]--; + dev->num_inputs--; + } /* how many outputs do we have and of what type? */ dev->num_outputs = num_outputs[inst]; @@ -696,6 +702,15 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) dev->output_name_counter[i] = out_type_counter[dev->output_type[i]]++; } dev->has_audio_outputs = out_type_counter[SVID]; + if (out_type_counter[HDMI] == 16) { + /* + * The CEC physical address only allows for max 15 inputs, + * so outputs are also limited to 15 to allow for easy + * CEC output to input mapping. + */ + out_type_counter[HDMI]--; + dev->num_outputs--; + } /* do we create a video capture device? */ dev->has_vid_cap = node_type & 0x0001; @@ -1010,6 +1025,17 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) INIT_LIST_HEAD(&dev->vbi_out_active); INIT_LIST_HEAD(&dev->sdr_cap_active); + INIT_LIST_HEAD(&dev->cec_work_list); + spin_lock_init(&dev->cec_slock); + /* + * Same as create_singlethread_workqueue, but now I can use the + * string formatting of alloc_ordered_workqueue. + */ + dev->cec_workqueue = + alloc_ordered_workqueue("vivid-%03d-cec", WQ_MEM_RECLAIM, inst); + if (!dev->cec_workqueue) + goto unreg_dev; + /* start creating the vb2 queues */ if (dev->has_vid_cap) { /* initialize vid_cap queue */ @@ -1117,7 +1143,8 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) /* finally start creating the device nodes */ if (dev->has_vid_cap) { vfd = &dev->vid_cap_dev; - strlcpy(vfd->name, "vivid-vid-cap", sizeof(vfd->name)); + snprintf(vfd->name, sizeof(vfd->name), + "vivid-%03d-vid-cap", inst); vfd->fops = &vivid_fops; vfd->ioctl_ops = &vivid_ioctl_ops; vfd->device_caps = dev->vid_cap_caps; @@ -1133,6 +1160,27 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) vfd->lock = &dev->mutex; video_set_drvdata(vfd, dev); +#ifdef CONFIG_VIDEO_VIVID_CEC + if (in_type_counter[HDMI]) { + struct cec_adapter *adap; + + adap = vivid_cec_alloc_adap(dev, 0, &pdev->dev, false); + ret = PTR_ERR_OR_ZERO(adap); + if (ret < 0) + goto unreg_dev; + dev->cec_rx_adap = adap; + ret = cec_register_adapter(adap); + if (ret < 0) { + cec_delete_adapter(adap); + dev->cec_rx_adap = NULL; + goto unreg_dev; + } + cec_s_phys_addr(adap, 0, false); + v4l2_info(&dev->v4l2_dev, "CEC adapter %s registered for HDMI input %d\n", + dev_name(&adap->devnode.dev), i); + } +#endif + ret = video_register_device(vfd, VFL_TYPE_GRABBER, vid_cap_nr[inst]); if (ret < 0) goto unreg_dev; @@ -1141,8 +1189,13 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) } if (dev->has_vid_out) { +#ifdef CONFIG_VIDEO_VIVID_CEC + unsigned int bus_cnt = 0; +#endif + vfd = &dev->vid_out_dev; - strlcpy(vfd->name, "vivid-vid-out", sizeof(vfd->name)); + snprintf(vfd->name, sizeof(vfd->name), + "vivid-%03d-vid-out", inst); vfd->vfl_dir = VFL_DIR_TX; vfd->fops = &vivid_fops; vfd->ioctl_ops = &vivid_ioctl_ops; @@ -1159,6 +1212,35 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) vfd->lock = &dev->mutex; video_set_drvdata(vfd, dev); +#ifdef CONFIG_VIDEO_VIVID_CEC + for (i = 0; i < dev->num_outputs; i++) { + struct cec_adapter *adap; + + if (dev->output_type[i] != HDMI) + continue; + dev->cec_output2bus_map[i] = bus_cnt; + adap = vivid_cec_alloc_adap(dev, bus_cnt, + &pdev->dev, true); + ret = PTR_ERR_OR_ZERO(adap); + if (ret < 0) + goto unreg_dev; + dev->cec_tx_adap[bus_cnt] = adap; + ret = cec_register_adapter(adap); + if (ret < 0) { + cec_delete_adapter(adap); + dev->cec_tx_adap[bus_cnt] = NULL; + goto unreg_dev; + } + bus_cnt++; + if (bus_cnt <= in_type_counter[HDMI]) + cec_s_phys_addr(adap, bus_cnt << 12, false); + else + cec_s_phys_addr(adap, 0x1000, false); + v4l2_info(&dev->v4l2_dev, "CEC adapter %s registered for HDMI output %d\n", + dev_name(&adap->devnode.dev), i); + } +#endif + ret = video_register_device(vfd, VFL_TYPE_GRABBER, vid_out_nr[inst]); if (ret < 0) goto unreg_dev; @@ -1168,7 +1250,8 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) if (dev->has_vbi_cap) { vfd = &dev->vbi_cap_dev; - strlcpy(vfd->name, "vivid-vbi-cap", sizeof(vfd->name)); + snprintf(vfd->name, sizeof(vfd->name), + "vivid-%03d-vbi-cap", inst); vfd->fops = &vivid_fops; vfd->ioctl_ops = &vivid_ioctl_ops; vfd->device_caps = dev->vbi_cap_caps; @@ -1191,7 +1274,8 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) if (dev->has_vbi_out) { vfd = &dev->vbi_out_dev; - strlcpy(vfd->name, "vivid-vbi-out", sizeof(vfd->name)); + snprintf(vfd->name, sizeof(vfd->name), + "vivid-%03d-vbi-out", inst); vfd->vfl_dir = VFL_DIR_TX; vfd->fops = &vivid_fops; vfd->ioctl_ops = &vivid_ioctl_ops; @@ -1215,7 +1299,8 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) if (dev->has_sdr_cap) { vfd = &dev->sdr_cap_dev; - strlcpy(vfd->name, "vivid-sdr-cap", sizeof(vfd->name)); + snprintf(vfd->name, sizeof(vfd->name), + "vivid-%03d-sdr-cap", inst); vfd->fops = &vivid_fops; vfd->ioctl_ops = &vivid_ioctl_ops; vfd->device_caps = dev->sdr_cap_caps; @@ -1234,7 +1319,8 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) if (dev->has_radio_rx) { vfd = &dev->radio_rx_dev; - strlcpy(vfd->name, "vivid-rad-rx", sizeof(vfd->name)); + snprintf(vfd->name, sizeof(vfd->name), + "vivid-%03d-rad-rx", inst); vfd->fops = &vivid_radio_fops; vfd->ioctl_ops = &vivid_ioctl_ops; vfd->device_caps = dev->radio_rx_caps; @@ -1252,7 +1338,8 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) if (dev->has_radio_tx) { vfd = &dev->radio_tx_dev; - strlcpy(vfd->name, "vivid-rad-tx", sizeof(vfd->name)); + snprintf(vfd->name, sizeof(vfd->name), + "vivid-%03d-rad-tx", inst); vfd->vfl_dir = VFL_DIR_TX; vfd->fops = &vivid_radio_fops; vfd->ioctl_ops = &vivid_ioctl_ops; @@ -1282,6 +1369,13 @@ unreg_dev: video_unregister_device(&dev->vbi_cap_dev); video_unregister_device(&dev->vid_out_dev); video_unregister_device(&dev->vid_cap_dev); + cec_unregister_adapter(dev->cec_rx_adap); + for (i = 0; i < MAX_OUTPUTS; i++) + cec_unregister_adapter(dev->cec_tx_adap[i]); + if (dev->cec_workqueue) { + vivid_cec_bus_free_work(dev); + destroy_workqueue(dev->cec_workqueue); + } free_dev: v4l2_device_put(&dev->v4l2_dev); return ret; @@ -1331,8 +1425,7 @@ static int vivid_probe(struct platform_device *pdev) static int vivid_remove(struct platform_device *pdev) { struct vivid_dev *dev; - unsigned i; - + unsigned int i, j; for (i = 0; i < n_devs; i++) { dev = vivid_devs[i]; @@ -1380,6 +1473,13 @@ static int vivid_remove(struct platform_device *pdev) unregister_framebuffer(&dev->fb_info); vivid_fb_release_buffers(dev); } + cec_unregister_adapter(dev->cec_rx_adap); + for (j = 0; j < MAX_OUTPUTS; j++) + cec_unregister_adapter(dev->cec_tx_adap[j]); + if (dev->cec_workqueue) { + vivid_cec_bus_free_work(dev); + destroy_workqueue(dev->cec_workqueue); + } v4l2_device_put(&dev->v4l2_dev); vivid_devs[i] = NULL; } diff --git a/drivers/media/platform/vivid/vivid-core.h b/drivers/media/platform/vivid/vivid-core.h index 776783bec227..a7daa40d0a49 100644 --- a/drivers/media/platform/vivid/vivid-core.h +++ b/drivers/media/platform/vivid/vivid-core.h @@ -21,6 +21,8 @@ #define _VIVID_CORE_H_ #include <linux/fb.h> +#include <linux/workqueue.h> +#include <media/cec.h> #include <media/videobuf2-v4l2.h> #include <media/v4l2-device.h> #include <media/v4l2-dev.h> @@ -132,6 +134,17 @@ enum vivid_colorspace { #define VIVID_INVALID_SIGNAL(mode) \ ((mode) == NO_SIGNAL || (mode) == NO_LOCK || (mode) == OUT_OF_RANGE) +struct vivid_cec_work { + struct list_head list; + struct delayed_work work; + struct cec_adapter *adap; + struct vivid_dev *dev; + unsigned int usecs; + unsigned int timeout_ms; + u8 tx_status; + struct cec_msg msg; +}; + struct vivid_dev { unsigned inst; struct v4l2_device v4l2_dev; @@ -497,6 +510,20 @@ struct vivid_dev { /* Shared between radio receiver and transmitter */ bool radio_rds_loop; struct timespec radio_rds_init_ts; + + /* CEC */ + struct cec_adapter *cec_rx_adap; + struct cec_adapter *cec_tx_adap[MAX_OUTPUTS]; + struct workqueue_struct *cec_workqueue; + spinlock_t cec_slock; + struct list_head cec_work_list; + unsigned int cec_xfer_time_jiffies; + unsigned long cec_xfer_start_jiffies; + u8 cec_output2bus_map[MAX_OUTPUTS]; + + /* CEC OSD String */ + char osd[14]; + unsigned long osd_jiffies; }; static inline bool vivid_is_webcam(const struct vivid_dev *dev) diff --git a/drivers/media/platform/vivid/vivid-kthread-cap.c b/drivers/media/platform/vivid/vivid-kthread-cap.c index 3b8c10108dfa..6ca71aabb576 100644 --- a/drivers/media/platform/vivid/vivid-kthread-cap.c +++ b/drivers/media/platform/vivid/vivid-kthread-cap.c @@ -552,6 +552,19 @@ static void vivid_fillbuff(struct vivid_dev *dev, struct vivid_buffer *buf) snprintf(str, sizeof(str), " button pressed!"); tpg_gen_text(tpg, basep, line++ * line_height, 16, str); } + if (dev->osd[0]) { + if (vivid_is_hdmi_cap(dev)) { + snprintf(str, sizeof(str), + " OSD \"%s\"", dev->osd); + tpg_gen_text(tpg, basep, line++ * line_height, + 16, str); + } + if (dev->osd_jiffies && + time_is_before_jiffies(dev->osd_jiffies + 5 * HZ)) { + dev->osd[0] = 0; + dev->osd_jiffies = 0; + } + } } /* diff --git a/drivers/media/platform/vivid/vivid-sdr-cap.c b/drivers/media/platform/vivid/vivid-sdr-cap.c index 3d1604cb982f..1428e31a2875 100644 --- a/drivers/media/platform/vivid/vivid-sdr-cap.c +++ b/drivers/media/platform/vivid/vivid-sdr-cap.c @@ -51,8 +51,6 @@ static const struct vivid_format formats[] = { }, }; -static const unsigned int NUM_FORMATS = ARRAY_SIZE(formats); - static const struct v4l2_frequency_band bands_adc[] = { { .tuner = 0, diff --git a/drivers/media/platform/vivid/vivid-vid-cap.c b/drivers/media/platform/vivid/vivid-vid-cap.c index 4f730f355a17..d625a6353f3d 100644 --- a/drivers/media/platform/vivid/vivid-vid-cap.c +++ b/drivers/media/platform/vivid/vivid-vid-cap.c @@ -36,8 +36,7 @@ /* timeperframe: min/max and default */ static const struct v4l2_fract tpf_min = {.numerator = 1, .denominator = FPS_MAX}, - tpf_max = {.numerator = FPS_MAX, .denominator = 1}, - tpf_default = {.numerator = 1, .denominator = 30}; + tpf_max = {.numerator = FPS_MAX, .denominator = 1}; static const struct vivid_fmt formats_ovl[] = { { @@ -1701,6 +1700,9 @@ int vidioc_s_edid(struct file *file, void *_fh, struct v4l2_edid *edid) { struct vivid_dev *dev = video_drvdata(file); + u16 phys_addr; + unsigned int i; + int ret; memset(edid->reserved, 0, sizeof(edid->reserved)); if (edid->pad >= dev->num_inputs) @@ -1709,14 +1711,32 @@ int vidioc_s_edid(struct file *file, void *_fh, return -EINVAL; if (edid->blocks == 0) { dev->edid_blocks = 0; - return 0; + phys_addr = CEC_PHYS_ADDR_INVALID; + goto set_phys_addr; } if (edid->blocks > dev->edid_max_blocks) { edid->blocks = dev->edid_max_blocks; return -E2BIG; } + phys_addr = cec_get_edid_phys_addr(edid->edid, edid->blocks * 128, NULL); + ret = cec_phys_addr_validate(phys_addr, &phys_addr, NULL); + if (ret) + return ret; + + if (vb2_is_busy(&dev->vb_vid_cap_q)) + return -EBUSY; + dev->edid_blocks = edid->blocks; memcpy(dev->edid, edid->edid, edid->blocks * 128); + +set_phys_addr: + /* TODO: a proper hotplug detect cycle should be emulated here */ + cec_s_phys_addr(dev->cec_rx_adap, phys_addr, false); + + for (i = 0; i < MAX_OUTPUTS && dev->cec_tx_adap[i]; i++) + cec_s_phys_addr(dev->cec_tx_adap[i], + cec_phys_addr_for_input(phys_addr, i + 1), + false); return 0; } diff --git a/drivers/media/platform/vivid/vivid-vid-common.c b/drivers/media/platform/vivid/vivid-vid-common.c index 39ea2284789c..fcda3ae4e6b0 100644 --- a/drivers/media/platform/vivid/vivid-vid-common.c +++ b/drivers/media/platform/vivid/vivid-vid-common.c @@ -811,6 +811,7 @@ int vidioc_g_edid(struct file *file, void *_fh, { struct vivid_dev *dev = video_drvdata(file); struct video_device *vdev = video_devdata(file); + struct cec_adapter *adap; memset(edid->reserved, 0, sizeof(edid->reserved)); if (vdev->vfl_dir == VFL_DIR_RX) { @@ -818,11 +819,16 @@ int vidioc_g_edid(struct file *file, void *_fh, return -EINVAL; if (dev->input_type[edid->pad] != HDMI) return -EINVAL; + adap = dev->cec_rx_adap; } else { + unsigned int bus_idx; + if (edid->pad >= dev->num_outputs) return -EINVAL; if (dev->output_type[edid->pad] != HDMI) return -EINVAL; + bus_idx = dev->cec_output2bus_map[edid->pad]; + adap = dev->cec_tx_adap[bus_idx]; } if (edid->start_block == 0 && edid->blocks == 0) { edid->blocks = dev->edid_blocks; @@ -835,5 +841,6 @@ int vidioc_g_edid(struct file *file, void *_fh, if (edid->start_block + edid->blocks > dev->edid_blocks) edid->blocks = dev->edid_blocks - edid->start_block; memcpy(edid->edid, dev->edid, edid->blocks * 128); + cec_set_edid_phys_addr(edid->edid, edid->blocks * 128, adap->phys_addr); return 0; } diff --git a/drivers/media/radio/radio-aztech.c b/drivers/media/radio/radio-aztech.c index 705dd6f9162c..f445327f282d 100644 --- a/drivers/media/radio/radio-aztech.c +++ b/drivers/media/radio/radio-aztech.c @@ -43,7 +43,6 @@ MODULE_VERSION("1.0.0"); static int io[AZTECH_MAX] = { [0] = CONFIG_RADIO_AZTECH_PORT, [1 ... (AZTECH_MAX - 1)] = -1 }; static int radio_nr[AZTECH_MAX] = { [0 ... (AZTECH_MAX - 1)] = -1 }; -static const int radio_wait_time = 1000; module_param_array(io, int, NULL, 0444); MODULE_PARM_DESC(io, "I/O addresses of the Aztech card (0x350 or 0x358)"); diff --git a/drivers/media/radio/radio-maxiradio.c b/drivers/media/radio/radio-maxiradio.c index 70fd8e80198a..8253f79d5d75 100644 --- a/drivers/media/radio/radio-maxiradio.c +++ b/drivers/media/radio/radio-maxiradio.c @@ -183,6 +183,7 @@ static void maxiradio_remove(struct pci_dev *pdev) outb(0, dev->io); v4l2_device_unregister(v4l2_dev); release_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0)); + kfree(dev); } static struct pci_device_id maxiradio_pci_tbl[] = { diff --git a/drivers/media/rc/rc-main.c b/drivers/media/rc/rc-main.c index 7dfc7c2188f0..c717eafd9d22 100644 --- a/drivers/media/rc/rc-main.c +++ b/drivers/media/rc/rc-main.c @@ -804,6 +804,7 @@ static const struct { { RC_BIT_SHARP, "sharp", "ir-sharp-decoder" }, { RC_BIT_MCE_KBD, "mce_kbd", "ir-mce_kbd-decoder" }, { RC_BIT_XMP, "xmp", "ir-xmp-decoder" }, + { RC_BIT_CEC, "cec", NULL }, }; /** diff --git a/drivers/media/tuners/mt2063.c b/drivers/media/tuners/mt2063.c index 6457ac91ef09..7f0b9d5940db 100644 --- a/drivers/media/tuners/mt2063.c +++ b/drivers/media/tuners/mt2063.c @@ -24,6 +24,7 @@ #include <linux/module.h> #include <linux/string.h> #include <linux/videodev2.h> +#include <linux/gcd.h> #include "mt2063.h" @@ -665,27 +666,6 @@ static u32 MT2063_ChooseFirstIF(struct MT2063_AvoidSpursData_t *pAS_Info) } /** - * gcd() - Uses Euclid's algorithm - * - * @u, @v: Unsigned values whose GCD is desired. - * - * Returns THE greatest common divisor of u and v, if either value is 0, - * the other value is returned as the result. - */ -static u32 MT2063_gcd(u32 u, u32 v) -{ - u32 r; - - while (v != 0) { - r = u % v; - u = v; - v = r; - } - - return u; -} - -/** * IsSpurInBand() - Checks to see if a spur will be present within the IF's * bandwidth. (fIFOut +/- fIFBW, -fIFOut +/- fIFBW) * @@ -731,12 +711,12 @@ static u32 IsSpurInBand(struct MT2063_AvoidSpursData_t *pAS_Info, ** of f_LO1, f_LO2 and the edge value. Use the larger of this ** gcd-based scale factor or f_Scale. */ - lo_gcd = MT2063_gcd(f_LO1, f_LO2); - gd_Scale = max((u32) MT2063_gcd(lo_gcd, d), f_Scale); + lo_gcd = gcd(f_LO1, f_LO2); + gd_Scale = max((u32) gcd(lo_gcd, d), f_Scale); hgds = gd_Scale / 2; - gc_Scale = max((u32) MT2063_gcd(lo_gcd, c), f_Scale); + gc_Scale = max((u32) gcd(lo_gcd, c), f_Scale); hgcs = gc_Scale / 2; - gf_Scale = max((u32) MT2063_gcd(lo_gcd, f), f_Scale); + gf_Scale = max((u32) gcd(lo_gcd, f), f_Scale); hgfs = gf_Scale / 2; n0 = DIV_ROUND_UP(f_LO2 - d, f_LO1 - f_LO2); diff --git a/drivers/media/tuners/r820t.c b/drivers/media/tuners/r820t.c index 6ab35e315fe7..08dca40356d2 100644 --- a/drivers/media/tuners/r820t.c +++ b/drivers/media/tuners/r820t.c @@ -337,20 +337,6 @@ static int r820t_xtal_capacitor[][2] = { }; /* - * measured with a Racal 6103E GSM test set at 928 MHz with -60 dBm - * input power, for raw results see: - * http://steve-m.de/projects/rtl-sdr/gain_measurement/r820t/ - */ - -static const int r820t_lna_gain_steps[] = { - 0, 9, 13, 40, 38, 13, 31, 22, 26, 31, 26, 14, 19, 5, 35, 13 -}; - -static const int r820t_mixer_gain_steps[] = { - 0, 5, 10, 10, 19, 9, 10, 25, 17, 10, 8, 16, 13, 6, 3, -8 -}; - -/* * I2C read/write code and shadow registers logic */ static void shadow_store(struct r820t_priv *priv, u8 reg, const u8 *val, @@ -1216,6 +1202,21 @@ static int r820t_read_gain(struct r820t_priv *priv) #if 0 /* FIXME: This routine requires more testing */ + +/* + * measured with a Racal 6103E GSM test set at 928 MHz with -60 dBm + * input power, for raw results see: + * http://steve-m.de/projects/rtl-sdr/gain_measurement/r820t/ + */ + +static const int r820t_lna_gain_steps[] = { + 0, 9, 13, 40, 38, 13, 31, 22, 26, 31, 26, 14, 19, 5, 35, 13 +}; + +static const int r820t_mixer_gain_steps[] = { + 0, 5, 10, 10, 19, 9, 10, 25, 17, 10, 8, 16, 13, 6, 3, -8 +}; + static int r820t_set_gain_mode(struct r820t_priv *priv, bool set_manual_gain, int gain) diff --git a/drivers/media/usb/au0828/au0828-core.c b/drivers/media/usb/au0828/au0828-core.c index 321ea5cf1329..bf53553d2624 100644 --- a/drivers/media/usb/au0828/au0828-core.c +++ b/drivers/media/usb/au0828/au0828-core.c @@ -142,7 +142,7 @@ static void au0828_unregister_media_device(struct au0828_dev *dev) struct media_device *mdev = dev->media_dev; struct media_entity_notify *notify, *nextp; - if (!mdev || !media_devnode_is_registered(&mdev->devnode)) + if (!mdev || !media_devnode_is_registered(mdev->devnode)) return; /* Remove au0828 entity_notify callbacks */ @@ -482,7 +482,7 @@ static int au0828_media_device_register(struct au0828_dev *dev, if (!dev->media_dev) return 0; - if (!media_devnode_is_registered(&dev->media_dev->devnode)) { + if (!media_devnode_is_registered(dev->media_dev->devnode)) { /* register media device */ ret = media_device_register(dev->media_dev); diff --git a/drivers/media/usb/dvb-usb-v2/Kconfig b/drivers/media/usb/dvb-usb-v2/Kconfig index 3dc8ef004f8b..524533d3eb29 100644 --- a/drivers/media/usb/dvb-usb-v2/Kconfig +++ b/drivers/media/usb/dvb-usb-v2/Kconfig @@ -127,17 +127,22 @@ config DVB_USB_MXL111SF config DVB_USB_RTL28XXU tristate "Realtek RTL28xxU DVB USB support" depends on DVB_USB_V2 && I2C_MUX + select DVB_MN88472 if MEDIA_SUBDRV_AUTOSELECT + select DVB_MN88473 if MEDIA_SUBDRV_AUTOSELECT select DVB_RTL2830 select DVB_RTL2832 select DVB_RTL2832_SDR if (MEDIA_SUBDRV_AUTOSELECT && MEDIA_SDR_SUPPORT) - select MEDIA_TUNER_QT1010 if MEDIA_SUBDRV_AUTOSELECT - select MEDIA_TUNER_MT2060 if MEDIA_SUBDRV_AUTOSELECT - select MEDIA_TUNER_MXL5005S if MEDIA_SUBDRV_AUTOSELECT + select DVB_SI2168 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_E4000 if MEDIA_SUBDRV_AUTOSELECT select MEDIA_TUNER_FC0012 if MEDIA_SUBDRV_AUTOSELECT select MEDIA_TUNER_FC0013 if MEDIA_SUBDRV_AUTOSELECT - select MEDIA_TUNER_E4000 if MEDIA_SUBDRV_AUTOSELECT select MEDIA_TUNER_FC2580 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_MT2060 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_MXL5005S if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_QT1010 if MEDIA_SUBDRV_AUTOSELECT select MEDIA_TUNER_R820T if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_SI2157 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_TUA9001 if MEDIA_SUBDRV_AUTOSELECT help Say Y here to support the Realtek RTL28xxU DVB USB receiver. diff --git a/drivers/media/usb/dvb-usb-v2/af9035.c b/drivers/media/usb/dvb-usb-v2/af9035.c index 2638e3251f2a..eabede44ad88 100644 --- a/drivers/media/usb/dvb-usb-v2/af9035.c +++ b/drivers/media/usb/dvb-usb-v2/af9035.c @@ -49,6 +49,7 @@ static int af9035_ctrl_msg(struct dvb_usb_device *d, struct usb_req *req) #define CHECKSUM_LEN 2 #define USB_TIMEOUT 2000 struct state *state = d_to_priv(d); + struct usb_interface *intf = d->intf; int ret, wlen, rlen; u16 checksum, tmp_checksum; @@ -57,8 +58,8 @@ static int af9035_ctrl_msg(struct dvb_usb_device *d, struct usb_req *req) /* buffer overflow check */ if (req->wlen > (BUF_LEN - REQ_HDR_LEN - CHECKSUM_LEN) || req->rlen > (BUF_LEN - ACK_HDR_LEN - CHECKSUM_LEN)) { - dev_err(&d->udev->dev, "%s: too much data wlen=%d rlen=%d\n", - KBUILD_MODNAME, req->wlen, req->rlen); + dev_err(&intf->dev, "too much data wlen=%d rlen=%d\n", + req->wlen, req->rlen); ret = -EINVAL; goto exit; } @@ -94,10 +95,8 @@ static int af9035_ctrl_msg(struct dvb_usb_device *d, struct usb_req *req) checksum = af9035_checksum(state->buf, rlen - 2); tmp_checksum = (state->buf[rlen - 2] << 8) | state->buf[rlen - 1]; if (tmp_checksum != checksum) { - dev_err(&d->udev->dev, - "%s: command=%02x checksum mismatch (%04x != %04x)\n", - KBUILD_MODNAME, req->cmd, tmp_checksum, - checksum); + dev_err(&intf->dev, "command=%02x checksum mismatch (%04x != %04x)\n", + req->cmd, tmp_checksum, checksum); ret = -EIO; goto exit; } @@ -110,8 +109,8 @@ static int af9035_ctrl_msg(struct dvb_usb_device *d, struct usb_req *req) goto exit; } - dev_dbg(&d->udev->dev, "%s: command=%02x failed fw error=%d\n", - __func__, req->cmd, state->buf[2]); + dev_dbg(&intf->dev, "command=%02x failed fw error=%d\n", + req->cmd, state->buf[2]); ret = -EIO; goto exit; } @@ -122,20 +121,20 @@ static int af9035_ctrl_msg(struct dvb_usb_device *d, struct usb_req *req) exit: mutex_unlock(&d->usb_mutex); if (ret < 0) - dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + dev_dbg(&intf->dev, "failed=%d\n", ret); return ret; } /* write multiple registers */ static int af9035_wr_regs(struct dvb_usb_device *d, u32 reg, u8 *val, int len) { + struct usb_interface *intf = d->intf; u8 wbuf[MAX_XFER_SIZE]; u8 mbox = (reg >> 16) & 0xff; struct usb_req req = { CMD_MEM_WR, mbox, 6 + len, wbuf, 0, NULL }; if (6 + len > sizeof(wbuf)) { - dev_warn(&d->udev->dev, "%s: i2c wr: len=%d is too big!\n", - KBUILD_MODNAME, len); + dev_warn(&intf->dev, "i2c wr: len=%d is too big!\n", len); return -EOPNOTSUPP; } @@ -198,6 +197,7 @@ static int af9035_add_i2c_dev(struct dvb_usb_device *d, const char *type, { int ret, num; struct state *state = d_to_priv(d); + struct usb_interface *intf = d->intf; struct i2c_client *client; struct i2c_board_info board_info = { .addr = addr, @@ -212,11 +212,10 @@ static int af9035_add_i2c_dev(struct dvb_usb_device *d, const char *type, break; } - dev_dbg(&d->udev->dev, "%s: num=%d\n", __func__, num); + dev_dbg(&intf->dev, "num=%d\n", num); if (num == AF9035_I2C_CLIENT_MAX) { - dev_err(&d->udev->dev, "%s: I2C client out of index\n", - KBUILD_MODNAME); + dev_err(&intf->dev, "I2C client out of index\n"); ret = -ENODEV; goto err; } @@ -240,7 +239,7 @@ static int af9035_add_i2c_dev(struct dvb_usb_device *d, const char *type, state->i2c_client[num] = client; return 0; err: - dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + dev_dbg(&intf->dev, "failed=%d\n", ret); return ret; } @@ -248,6 +247,7 @@ static void af9035_del_i2c_dev(struct dvb_usb_device *d) { int num; struct state *state = d_to_priv(d); + struct usb_interface *intf = d->intf; struct i2c_client *client; /* find last used client */ @@ -257,11 +257,10 @@ static void af9035_del_i2c_dev(struct dvb_usb_device *d) break; } - dev_dbg(&d->udev->dev, "%s: num=%d\n", __func__, num); + dev_dbg(&intf->dev, "num=%d\n", num); if (num == -1) { - dev_err(&d->udev->dev, "%s: I2C client out of index\n", - KBUILD_MODNAME); + dev_err(&intf->dev, "I2C client out of index\n"); goto err; } @@ -276,7 +275,7 @@ static void af9035_del_i2c_dev(struct dvb_usb_device *d) state->i2c_client[num] = NULL; return; err: - dev_dbg(&d->udev->dev, "%s: failed\n", __func__); + dev_dbg(&intf->dev, "failed\n"); } static int af9035_i2c_master_xfer(struct i2c_adapter *adap, @@ -348,6 +347,9 @@ static int af9035_i2c_master_xfer(struct i2c_adapter *adap, ret = af9035_rd_regs(d, reg, &msg[1].buf[0], msg[1].len); + } else if (state->no_read) { + memset(msg[1].buf, 0, msg[1].len); + ret = 0; } else { /* I2C write + read */ u8 buf[MAX_XFER_SIZE]; @@ -367,10 +369,25 @@ static int af9035_i2c_master_xfer(struct i2c_adapter *adap, memcpy(&buf[3], msg[0].buf, msg[0].len); } else { buf[1] = msg[0].addr << 1; - buf[2] = 0x00; /* reg addr len */ buf[3] = 0x00; /* reg addr MSB */ buf[4] = 0x00; /* reg addr LSB */ - memcpy(&buf[5], msg[0].buf, msg[0].len); + + /* Keep prev behavior for write req len > 2*/ + if (msg[0].len > 2) { + buf[2] = 0x00; /* reg addr len */ + memcpy(&buf[5], msg[0].buf, msg[0].len); + + /* Use reg addr fields if write req len <= 2 */ + } else { + req.wlen = 5; + buf[2] = msg[0].len; + if (msg[0].len == 2) { + buf[3] = msg[0].buf[0]; + buf[4] = msg[0].buf[1]; + } else if (msg[0].len == 1) { + buf[4] = msg[0].buf[0]; + } + } } ret = af9035_ctrl_msg(d, &req); } @@ -421,6 +438,9 @@ static int af9035_i2c_master_xfer(struct i2c_adapter *adap, if (msg[0].len > 40) { /* TODO: correct limits > 40 */ ret = -EOPNOTSUPP; + } else if (state->no_read) { + memset(msg[0].buf, 0, msg[0].len); + ret = 0; } else { /* I2C read */ u8 buf[5]; @@ -475,6 +495,7 @@ static struct i2c_algorithm af9035_i2c_algo = { static int af9035_identify_state(struct dvb_usb_device *d, const char **name) { struct state *state = d_to_priv(d); + struct usb_interface *intf = d->intf; int ret; u8 wbuf[1] = { 1 }; u8 rbuf[4]; @@ -492,10 +513,8 @@ static int af9035_identify_state(struct dvb_usb_device *d, const char **name) if (ret < 0) goto err; - dev_info(&d->udev->dev, - "%s: prechip_version=%02x chip_version=%02x chip_type=%04x\n", - KBUILD_MODNAME, state->prechip_version, - state->chip_version, state->chip_type); + dev_info(&intf->dev, "prechip_version=%02x chip_version=%02x chip_type=%04x\n", + state->prechip_version, state->chip_version, state->chip_type); if (state->chip_type == 0x9135) { if (state->chip_version == 0x02) @@ -515,7 +534,7 @@ static int af9035_identify_state(struct dvb_usb_device *d, const char **name) if (ret < 0) goto err; - dev_dbg(&d->udev->dev, "%s: reply=%*ph\n", __func__, 4, rbuf); + dev_dbg(&intf->dev, "reply=%*ph\n", 4, rbuf); if (rbuf[0] || rbuf[1] || rbuf[2] || rbuf[3]) ret = WARM; else @@ -524,7 +543,7 @@ static int af9035_identify_state(struct dvb_usb_device *d, const char **name) return ret; err: - dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + dev_dbg(&intf->dev, "failed=%d\n", ret); return ret; } @@ -532,6 +551,7 @@ err: static int af9035_download_firmware_old(struct dvb_usb_device *d, const struct firmware *fw) { + struct usb_interface *intf = d->intf; int ret, i, j, len; u8 wbuf[1]; struct usb_req req = { 0, 0, 0, NULL, 0, NULL }; @@ -562,14 +582,12 @@ static int af9035_download_firmware_old(struct dvb_usb_device *d, hdr_checksum = fw->data[fw->size - i + 5] << 8; hdr_checksum |= fw->data[fw->size - i + 6] << 0; - dev_dbg(&d->udev->dev, - "%s: core=%d addr=%04x data_len=%d checksum=%04x\n", - __func__, hdr_core, hdr_addr, hdr_data_len, - hdr_checksum); + dev_dbg(&intf->dev, "core=%d addr=%04x data_len=%d checksum=%04x\n", + hdr_core, hdr_addr, hdr_data_len, hdr_checksum); if (((hdr_core != 1) && (hdr_core != 2)) || (hdr_data_len > i)) { - dev_dbg(&d->udev->dev, "%s: bad firmware\n", __func__); + dev_dbg(&intf->dev, "bad firmware\n"); break; } @@ -600,18 +618,17 @@ static int af9035_download_firmware_old(struct dvb_usb_device *d, i -= hdr_data_len + HDR_SIZE; - dev_dbg(&d->udev->dev, "%s: data uploaded=%zu\n", - __func__, fw->size - i); + dev_dbg(&intf->dev, "data uploaded=%zu\n", fw->size - i); } /* print warn if firmware is bad, continue and see what happens */ if (i) - dev_warn(&d->udev->dev, "%s: bad firmware\n", KBUILD_MODNAME); + dev_warn(&intf->dev, "bad firmware\n"); return 0; err: - dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + dev_dbg(&intf->dev, "failed=%d\n", ret); return ret; } @@ -619,6 +636,7 @@ err: static int af9035_download_firmware_new(struct dvb_usb_device *d, const struct firmware *fw) { + struct usb_interface *intf = d->intf; int ret, i, i_prev; struct usb_req req_fw_dl = { CMD_FW_SCATTER_WR, 0, 0, NULL, 0, NULL }; #define HDR_SIZE 7 @@ -648,15 +666,14 @@ static int af9035_download_firmware_new(struct dvb_usb_device *d, if (ret < 0) goto err; - dev_dbg(&d->udev->dev, "%s: data uploaded=%d\n", - __func__, i); + dev_dbg(&intf->dev, "data uploaded=%d\n", i); } } return 0; err: - dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + dev_dbg(&intf->dev, "failed=%d\n", ret); return ret; } @@ -664,6 +681,7 @@ err: static int af9035_download_firmware(struct dvb_usb_device *d, const struct firmware *fw) { + struct usb_interface *intf = d->intf; struct state *state = d_to_priv(d); int ret; u8 wbuf[1]; @@ -672,7 +690,7 @@ static int af9035_download_firmware(struct dvb_usb_device *d, struct usb_req req = { 0, 0, 0, NULL, 0, NULL }; struct usb_req req_fw_ver = { CMD_FW_QUERYINFO, 0, 1, wbuf, 4, rbuf }; - dev_dbg(&d->udev->dev, "%s:\n", __func__); + dev_dbg(&intf->dev, "\n"); /* * In case of dual tuner configuration we need to do some extra @@ -752,25 +770,25 @@ static int af9035_download_firmware(struct dvb_usb_device *d, goto err; if (!(rbuf[0] || rbuf[1] || rbuf[2] || rbuf[3])) { - dev_err(&d->udev->dev, "%s: firmware did not run\n", - KBUILD_MODNAME); + dev_err(&intf->dev, "firmware did not run\n"); ret = -ENODEV; goto err; } - dev_info(&d->udev->dev, "%s: firmware version=%d.%d.%d.%d", - KBUILD_MODNAME, rbuf[0], rbuf[1], rbuf[2], rbuf[3]); + dev_info(&intf->dev, "firmware version=%d.%d.%d.%d", + rbuf[0], rbuf[1], rbuf[2], rbuf[3]); return 0; err: - dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + dev_dbg(&intf->dev, "failed=%d\n", ret); return ret; } static int af9035_read_config(struct dvb_usb_device *d) { + struct usb_interface *intf = d->intf; struct state *state = d_to_priv(d); int ret, i; u8 tmp; @@ -805,7 +823,7 @@ static int af9035_read_config(struct dvb_usb_device *d) goto err; if (tmp == 0x00) { - dev_dbg(&d->udev->dev, "%s: no eeprom\n", __func__); + dev_dbg(&intf->dev, "no eeprom\n"); goto skip_eeprom; } } else if (state->chip_type == 0x9306) { @@ -826,8 +844,7 @@ static int af9035_read_config(struct dvb_usb_device *d) if (tmp == 1 || tmp == 3 || tmp == 5) state->dual_mode = true; - dev_dbg(&d->udev->dev, "%s: ts mode=%d dual mode=%d\n", __func__, - tmp, state->dual_mode); + dev_dbg(&intf->dev, "ts mode=%d dual mode=%d\n", tmp, state->dual_mode); if (state->dual_mode) { /* read 2nd demodulator I2C address */ @@ -840,8 +857,7 @@ static int af9035_read_config(struct dvb_usb_device *d) if (tmp) state->af9033_i2c_addr[1] = tmp; - dev_dbg(&d->udev->dev, "%s: 2nd demod I2C addr=%02x\n", - __func__, tmp); + dev_dbg(&intf->dev, "2nd demod I2C addr=%02x\n", tmp); } addr = state->eeprom_addr; @@ -852,8 +868,7 @@ static int af9035_read_config(struct dvb_usb_device *d) if (ret < 0) goto err; - dev_dbg(&d->udev->dev, "%s: [%d]tuner=%02x\n", - __func__, i, tmp); + dev_dbg(&intf->dev, "[%d]tuner=%02x\n", i, tmp); /* tuner sanity check */ if (state->chip_type == 0x9135) { @@ -882,10 +897,8 @@ static int af9035_read_config(struct dvb_usb_device *d) } if (state->af9033_config[i].tuner != tmp) { - dev_info(&d->udev->dev, - "%s: [%d] overriding tuner from %02x to %02x\n", - KBUILD_MODNAME, i, tmp, - state->af9033_config[i].tuner); + dev_info(&intf->dev, "[%d] overriding tuner from %02x to %02x\n", + i, tmp, state->af9033_config[i].tuner); } switch (state->af9033_config[i].tuner) { @@ -905,9 +918,8 @@ static int af9035_read_config(struct dvb_usb_device *d) case AF9033_TUNER_IT9135_62: break; default: - dev_warn(&d->udev->dev, - "%s: tuner id=%02x not supported, please report!", - KBUILD_MODNAME, tmp); + dev_warn(&intf->dev, "tuner id=%02x not supported, please report!", + tmp); } /* disable dual mode if driver does not support it */ @@ -924,9 +936,7 @@ static int af9035_read_config(struct dvb_usb_device *d) break; default: state->dual_mode = false; - dev_info(&d->udev->dev, - "%s: driver does not support 2nd tuner and will disable it", - KBUILD_MODNAME); + dev_info(&intf->dev, "driver does not support 2nd tuner and will disable it"); } /* tuner IF frequency */ @@ -942,7 +952,7 @@ static int af9035_read_config(struct dvb_usb_device *d) tmp16 |= tmp << 8; - dev_dbg(&d->udev->dev, "%s: [%d]IF=%d\n", __func__, i, tmp16); + dev_dbg(&intf->dev, "[%d]IF=%d\n", i, tmp16); addr += 0x10; /* shift for the 2nd tuner params */ } @@ -962,10 +972,24 @@ skip_eeprom: state->af9033_config[i].clock = clock_lut_af9035[tmp]; } + state->no_read = false; + /* Some MXL5007T devices cannot properly handle tuner I2C read ops. */ + if (state->af9033_config[0].tuner == AF9033_TUNER_MXL5007T && + le16_to_cpu(d->udev->descriptor.idVendor) == USB_VID_AVERMEDIA) + + switch (le16_to_cpu(d->udev->descriptor.idProduct)) { + case USB_PID_AVERMEDIA_A867: + case USB_PID_AVERMEDIA_TWINSTAR: + dev_info(&intf->dev, + "Device may have issues with I2C read operations. Enabling fix.\n"); + state->no_read = true; + break; + } + return 0; err: - dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + dev_dbg(&intf->dev, "failed=%d\n", ret); return ret; } @@ -973,10 +997,11 @@ err: static int af9035_tua9001_tuner_callback(struct dvb_usb_device *d, int cmd, int arg) { + struct usb_interface *intf = d->intf; int ret; u8 val; - dev_dbg(&d->udev->dev, "%s: cmd=%d arg=%d\n", __func__, cmd, arg); + dev_dbg(&intf->dev, "cmd=%d arg=%d\n", cmd, arg); /* * CEN always enabled by hardware wiring @@ -1010,7 +1035,7 @@ static int af9035_tua9001_tuner_callback(struct dvb_usb_device *d, return 0; err: - dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + dev_dbg(&intf->dev, "failed=%d\n", ret); return ret; } @@ -1019,6 +1044,7 @@ err: static int af9035_fc0011_tuner_callback(struct dvb_usb_device *d, int cmd, int arg) { + struct usb_interface *intf = d->intf; int ret; switch (cmd) { @@ -1076,7 +1102,7 @@ static int af9035_fc0011_tuner_callback(struct dvb_usb_device *d, return 0; err: - dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + dev_dbg(&intf->dev, "failed=%d\n", ret); return ret; } @@ -1102,9 +1128,10 @@ static int af9035_frontend_callback(void *adapter_priv, int component, { struct i2c_adapter *adap = adapter_priv; struct dvb_usb_device *d = i2c_get_adapdata(adap); + struct usb_interface *intf = d->intf; - dev_dbg(&d->udev->dev, "%s: component=%d cmd=%d arg=%d\n", - __func__, component, cmd, arg); + dev_dbg(&intf->dev, "component=%d cmd=%d arg=%d\n", + component, cmd, arg); switch (component) { case DVB_FRONTEND_COMPONENT_TUNER: @@ -1127,9 +1154,10 @@ static int af9035_frontend_attach(struct dvb_usb_adapter *adap) { struct state *state = adap_to_priv(adap); struct dvb_usb_device *d = adap_to_d(adap); + struct usb_interface *intf = d->intf; int ret; - dev_dbg(&d->udev->dev, "%s: adap->id=%d\n", __func__, adap->id); + dev_dbg(&intf->dev, "adap->id=%d\n", adap->id); if (!state->af9033_config[adap->id].tuner) { /* unsupported tuner */ @@ -1156,7 +1184,7 @@ static int af9035_frontend_attach(struct dvb_usb_adapter *adap) return 0; err: - dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + dev_dbg(&intf->dev, "failed=%d\n", ret); return ret; } @@ -1165,11 +1193,12 @@ static int it930x_frontend_attach(struct dvb_usb_adapter *adap) { struct state *state = adap_to_priv(adap); struct dvb_usb_device *d = adap_to_d(adap); + struct usb_interface *intf = d->intf; int ret; struct si2168_config si2168_config; struct i2c_adapter *adapter; - dev_dbg(&d->udev->dev, "adap->id=%d\n", adap->id); + dev_dbg(&intf->dev, "adap->id=%d\n", adap->id); memset(&si2168_config, 0, sizeof(si2168_config)); si2168_config.i2c_adapter = &adapter; @@ -1192,7 +1221,7 @@ static int it930x_frontend_attach(struct dvb_usb_adapter *adap) return 0; err: - dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + dev_dbg(&intf->dev, "failed=%d\n", ret); return ret; } @@ -1201,9 +1230,10 @@ static int af9035_frontend_detach(struct dvb_usb_adapter *adap) { struct state *state = adap_to_priv(adap); struct dvb_usb_device *d = adap_to_d(adap); + struct usb_interface *intf = d->intf; int demod2; - dev_dbg(&d->udev->dev, "%s: adap->id=%d\n", __func__, adap->id); + dev_dbg(&intf->dev, "adap->id=%d\n", adap->id); /* * For dual tuner devices we have to resolve 2nd demod client, as there @@ -1279,12 +1309,13 @@ static int af9035_tuner_attach(struct dvb_usb_adapter *adap) { struct state *state = adap_to_priv(adap); struct dvb_usb_device *d = adap_to_d(adap); + struct usb_interface *intf = d->intf; int ret; struct dvb_frontend *fe; struct i2c_msg msg[1]; u8 tuner_addr; - dev_dbg(&d->udev->dev, "%s: adap->id=%d\n", __func__, adap->id); + dev_dbg(&intf->dev, "adap->id=%d\n", adap->id); /* * XXX: Hack used in that function: we abuse unused I2C address bit [7] @@ -1522,7 +1553,7 @@ static int af9035_tuner_attach(struct dvb_usb_adapter *adap) return 0; err: - dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + dev_dbg(&intf->dev, "failed=%d\n", ret); return ret; } @@ -1531,10 +1562,11 @@ static int it930x_tuner_attach(struct dvb_usb_adapter *adap) { struct state *state = adap_to_priv(adap); struct dvb_usb_device *d = adap_to_d(adap); + struct usb_interface *intf = d->intf; int ret; struct si2157_config si2157_config; - dev_dbg(&d->udev->dev, "%s: adap->id=%d\n", __func__, adap->id); + dev_dbg(&intf->dev, "adap->id=%d\n", adap->id); /* I2C master bus 2 clock speed 300k */ ret = af9035_wr_reg(d, 0x00f6a7, 0x07); @@ -1590,7 +1622,7 @@ static int it930x_tuner_attach(struct dvb_usb_adapter *adap) return 0; err: - dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + dev_dbg(&intf->dev, "failed=%d\n", ret); return ret; } @@ -1600,8 +1632,9 @@ static int it930x_tuner_detach(struct dvb_usb_adapter *adap) { struct state *state = adap_to_priv(adap); struct dvb_usb_device *d = adap_to_d(adap); + struct usb_interface *intf = d->intf; - dev_dbg(&d->udev->dev, "adap->id=%d\n", adap->id); + dev_dbg(&intf->dev, "adap->id=%d\n", adap->id); if (adap->id == 1) { if (state->i2c_client[3]) @@ -1619,8 +1652,9 @@ static int af9035_tuner_detach(struct dvb_usb_adapter *adap) { struct state *state = adap_to_priv(adap); struct dvb_usb_device *d = adap_to_d(adap); + struct usb_interface *intf = d->intf; - dev_dbg(&d->udev->dev, "%s: adap->id=%d\n", __func__, adap->id); + dev_dbg(&intf->dev, "adap->id=%d\n", adap->id); switch (state->af9033_config[adap->id].tuner) { case AF9033_TUNER_TUA9001: @@ -1646,6 +1680,7 @@ static int af9035_tuner_detach(struct dvb_usb_adapter *adap) static int af9035_init(struct dvb_usb_device *d) { struct state *state = d_to_priv(d); + struct usb_interface *intf = d->intf; int ret, i; u16 frame_size = (d->udev->speed == USB_SPEED_FULL ? 5 : 87) * 188 / 4; u8 packet_size = (d->udev->speed == USB_SPEED_FULL ? 64 : 512) / 4; @@ -1670,9 +1705,8 @@ static int af9035_init(struct dvb_usb_device *d) { 0x80f9a4, 0x00, 0x01 }, }; - dev_dbg(&d->udev->dev, - "%s: USB speed=%d frame_size=%04x packet_size=%02x\n", - __func__, d->udev->speed, frame_size, packet_size); + dev_dbg(&intf->dev, "USB speed=%d frame_size=%04x packet_size=%02x\n", + d->udev->speed, frame_size, packet_size); /* init endpoints */ for (i = 0; i < ARRAY_SIZE(tab); i++) { @@ -1685,7 +1719,7 @@ static int af9035_init(struct dvb_usb_device *d) return 0; err: - dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + dev_dbg(&intf->dev, "failed=%d\n", ret); return ret; } @@ -1693,6 +1727,7 @@ err: static int it930x_init(struct dvb_usb_device *d) { struct state *state = d_to_priv(d); + struct usb_interface *intf = d->intf; int ret, i; u16 frame_size = (d->udev->speed == USB_SPEED_FULL ? 5 : 816) * 188 / 4; u8 packet_size = (d->udev->speed == USB_SPEED_FULL ? 64 : 512) / 4; @@ -1752,9 +1787,8 @@ static int it930x_init(struct dvb_usb_device *d) { 0x00da5a, 0x1f, 0xff }, /* ts_fail_ignore */ }; - dev_dbg(&d->udev->dev, - "%s: USB speed=%d frame_size=%04x packet_size=%02x\n", - __func__, d->udev->speed, frame_size, packet_size); + dev_dbg(&intf->dev, "USB speed=%d frame_size=%04x packet_size=%02x\n", + d->udev->speed, frame_size, packet_size); /* init endpoints */ for (i = 0; i < ARRAY_SIZE(tab); i++) { @@ -1767,7 +1801,7 @@ static int it930x_init(struct dvb_usb_device *d) return 0; err: - dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + dev_dbg(&intf->dev, "failed=%d\n", ret); return ret; } @@ -1776,6 +1810,7 @@ err: #if IS_ENABLED(CONFIG_RC_CORE) static int af9035_rc_query(struct dvb_usb_device *d) { + struct usb_interface *intf = d->intf; int ret; u32 key; u8 buf[4]; @@ -1801,14 +1836,14 @@ static int af9035_rc_query(struct dvb_usb_device *d) buf[2] << 8 | buf[3]); } - dev_dbg(&d->udev->dev, "%s: %*ph\n", __func__, 4, buf); + dev_dbg(&intf->dev, "%*ph\n", 4, buf); rc_keydown(d->rc_dev, RC_TYPE_NEC, key, 0); return 0; err: - dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + dev_dbg(&intf->dev, "failed=%d\n", ret); return ret; } @@ -1816,6 +1851,7 @@ err: static int af9035_get_rc_config(struct dvb_usb_device *d, struct dvb_usb_rc *rc) { struct state *state = d_to_priv(d); + struct usb_interface *intf = d->intf; int ret; u8 tmp; @@ -1823,7 +1859,7 @@ static int af9035_get_rc_config(struct dvb_usb_device *d, struct dvb_usb_rc *rc) if (ret < 0) goto err; - dev_dbg(&d->udev->dev, "%s: ir_mode=%02x\n", __func__, tmp); + dev_dbg(&intf->dev, "ir_mode=%02x\n", tmp); /* don't activate rc if in HID mode or if not available */ if (tmp == 5) { @@ -1832,7 +1868,7 @@ static int af9035_get_rc_config(struct dvb_usb_device *d, struct dvb_usb_rc *rc) if (ret < 0) goto err; - dev_dbg(&d->udev->dev, "%s: ir_type=%02x\n", __func__, tmp); + dev_dbg(&intf->dev, "ir_type=%02x\n", tmp); switch (tmp) { case 0: /* NEC */ @@ -1855,7 +1891,7 @@ static int af9035_get_rc_config(struct dvb_usb_device *d, struct dvb_usb_rc *rc) return 0; err: - dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret); + dev_dbg(&intf->dev, "failed=%d\n", ret); return ret; } @@ -1867,8 +1903,9 @@ static int af9035_get_stream_config(struct dvb_frontend *fe, u8 *ts_type, struct usb_data_stream_properties *stream) { struct dvb_usb_device *d = fe_to_d(fe); + struct usb_interface *intf = d->intf; - dev_dbg(&d->udev->dev, "%s: adap=%d\n", __func__, fe_to_adap(fe)->id); + dev_dbg(&intf->dev, "adap=%d\n", fe_to_adap(fe)->id); if (d->udev->speed == USB_SPEED_FULL) stream->u.bulk.buffersize = 5 * 188; @@ -1920,7 +1957,7 @@ static int af9035_probe(struct usb_interface *intf, if ((le16_to_cpu(udev->descriptor.idVendor) == USB_VID_TERRATEC) && (le16_to_cpu(udev->descriptor.idProduct) == 0x0099)) { if (!strcmp("Afatech", manufacturer)) { - dev_dbg(&udev->dev, "%s: rejecting device\n", __func__); + dev_dbg(&udev->dev, "rejecting device\n"); return -ENODEV; } } diff --git a/drivers/media/usb/dvb-usb-v2/af9035.h b/drivers/media/usb/dvb-usb-v2/af9035.h index 89e629a24aec..c91d1a3789e6 100644 --- a/drivers/media/usb/dvb-usb-v2/af9035.h +++ b/drivers/media/usb/dvb-usb-v2/af9035.h @@ -62,6 +62,7 @@ struct state { u8 chip_version; u16 chip_type; u8 dual_mode:1; + u8 no_read:1; u16 eeprom_addr; u8 af9033_i2c_addr[2]; struct af9033_config af9033_config[2]; diff --git a/drivers/media/usb/dvb-usb-v2/rtl28xxu.c b/drivers/media/usb/dvb-usb-v2/rtl28xxu.c index eb7af8cb8aca..6643762a9ff7 100644 --- a/drivers/media/usb/dvb-usb-v2/rtl28xxu.c +++ b/drivers/media/usb/dvb-usb-v2/rtl28xxu.c @@ -624,7 +624,7 @@ static int rtl28xxu_identify_state(struct dvb_usb_device *d, const char **name) dev_dbg(&d->intf->dev, "chip_id=%u\n", dev->chip_id); /* Retry failed I2C messages */ - d->i2c_adap.retries = 1; + d->i2c_adap.retries = 3; d->i2c_adap.timeout = msecs_to_jiffies(10); return WARM; diff --git a/drivers/media/usb/em28xx/em28xx-dvb.c b/drivers/media/usb/em28xx/em28xx-dvb.c index 1a5c01202f73..8cedef0daae4 100644 --- a/drivers/media/usb/em28xx/em28xx-dvb.c +++ b/drivers/media/usb/em28xx/em28xx-dvb.c @@ -904,17 +904,6 @@ static struct tda18271_config c3tech_duo_tda18271_config = { .small_i2c = TDA18271_03_BYTE_CHUNK_INIT, }; -static const struct m88ds3103_config pctv_461e_m88ds3103_config = { - .i2c_addr = 0x68, - .clock = 27000000, - .i2c_wr_max = 33, - .clock_out = 0, - .ts_mode = M88DS3103_TS_PARALLEL, - .ts_clk = 16000, - .ts_clk_pol = 1, - .agc = 0x99, -}; - static struct tda18271_std_map drx_j_std_map = { .atsc_6 = { .if_freq = 5000, .agc_mode = 3, .std = 0, .if_lvl = 1, .rfagc_top = 0x37, }, diff --git a/drivers/media/usb/em28xx/em28xx-i2c.c b/drivers/media/usb/em28xx/em28xx-i2c.c index a19b5c8b56ff..1a9e1e556706 100644 --- a/drivers/media/usb/em28xx/em28xx-i2c.c +++ b/drivers/media/usb/em28xx/em28xx-i2c.c @@ -507,9 +507,8 @@ static int em28xx_i2c_xfer(struct i2c_adapter *i2c_adap, if (dev->disconnected) return -ENODEV; - rc = rt_mutex_trylock(&dev->i2c_bus_lock); - if (rc < 0) - return rc; + if (!rt_mutex_trylock(&dev->i2c_bus_lock)) + return -EAGAIN; /* Switch I2C bus if needed */ if (bus != dev->cur_i2c_bus && diff --git a/drivers/media/usb/gspca/m5602/m5602_bridge.h b/drivers/media/usb/gspca/m5602/m5602_bridge.h index 19eb1a64f9d6..43ebc03d844d 100644 --- a/drivers/media/usb/gspca/m5602/m5602_bridge.h +++ b/drivers/media/usb/gspca/m5602/m5602_bridge.h @@ -115,21 +115,6 @@ /*****************************************************************************/ -/* A skeleton used for sending messages to the m5602 bridge */ -static const unsigned char bridge_urb_skeleton[] = { - 0x13, 0x00, 0x81, 0x00 -}; - -/* A skeleton used for sending messages to the sensor */ -static const unsigned char sensor_urb_skeleton[] = { - 0x23, M5602_XB_GPIO_EN_H, 0x81, 0x06, - 0x23, M5602_XB_MISC_CTRL, 0x81, 0x80, - 0x13, M5602_XB_I2C_DEV_ADDR, 0x81, 0x00, - 0x13, M5602_XB_I2C_REG_ADDR, 0x81, 0x00, - 0x13, M5602_XB_I2C_DATA, 0x81, 0x00, - 0x13, M5602_XB_I2C_CTRL, 0x81, 0x11 -}; - struct sd { struct gspca_dev gspca_dev; diff --git a/drivers/media/usb/gspca/m5602/m5602_core.c b/drivers/media/usb/gspca/m5602/m5602_core.c index d926e62cb80b..e4a0658e3f83 100644 --- a/drivers/media/usb/gspca/m5602/m5602_core.c +++ b/drivers/media/usb/gspca/m5602/m5602_core.c @@ -37,6 +37,21 @@ static const struct usb_device_id m5602_table[] = { MODULE_DEVICE_TABLE(usb, m5602_table); +/* A skeleton used for sending messages to the sensor */ +static const unsigned char sensor_urb_skeleton[] = { + 0x23, M5602_XB_GPIO_EN_H, 0x81, 0x06, + 0x23, M5602_XB_MISC_CTRL, 0x81, 0x80, + 0x13, M5602_XB_I2C_DEV_ADDR, 0x81, 0x00, + 0x13, M5602_XB_I2C_REG_ADDR, 0x81, 0x00, + 0x13, M5602_XB_I2C_DATA, 0x81, 0x00, + 0x13, M5602_XB_I2C_CTRL, 0x81, 0x11 +}; + +/* A skeleton used for sending messages to the m5602 bridge */ +static const unsigned char bridge_urb_skeleton[] = { + 0x13, 0x00, 0x81, 0x00 +}; + /* Reads a byte from the m5602 */ int m5602_read_bridge(struct sd *sd, const u8 address, u8 *i2c_data) { diff --git a/drivers/media/usb/gspca/m5602/m5602_mt9m111.c b/drivers/media/usb/gspca/m5602/m5602_mt9m111.c index 27fcef11aef4..7d01ddd7ed01 100644 --- a/drivers/media/usb/gspca/m5602/m5602_mt9m111.c +++ b/drivers/media/usb/gspca/m5602/m5602_mt9m111.c @@ -23,6 +23,150 @@ static int mt9m111_s_ctrl(struct v4l2_ctrl *ctrl); static void mt9m111_dump_registers(struct sd *sd); +static const unsigned char preinit_mt9m111[][4] = { + {BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02, 0x00}, + {BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00}, + {BRIDGE, M5602_XB_SENSOR_TYPE, 0x0d, 0x00}, + {BRIDGE, M5602_XB_SENSOR_CTRL, 0x00, 0x00}, + {BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00}, + {BRIDGE, M5602_XB_SENSOR_TYPE, 0x09, 0x00}, + + {SENSOR, MT9M111_PAGE_MAP, 0x00, 0x00}, + {SENSOR, MT9M111_SC_RESET, + MT9M111_RESET | + MT9M111_RESTART | + MT9M111_ANALOG_STANDBY | + MT9M111_CHIP_DISABLE, + MT9M111_SHOW_BAD_FRAMES | + MT9M111_RESTART_BAD_FRAMES | + MT9M111_SYNCHRONIZE_CHANGES}, + + {BRIDGE, M5602_XB_GPIO_DIR, 0x05, 0x00}, + {BRIDGE, M5602_XB_GPIO_DAT, 0x04, 0x00}, + {BRIDGE, M5602_XB_GPIO_EN_H, 0x3e, 0x00}, + {BRIDGE, M5602_XB_GPIO_DIR_H, 0x3e, 0x00}, + {BRIDGE, M5602_XB_GPIO_DAT_H, 0x02, 0x00}, + {BRIDGE, M5602_XB_GPIO_EN_L, 0xff, 0x00}, + {BRIDGE, M5602_XB_GPIO_DIR_L, 0xff, 0x00}, + {BRIDGE, M5602_XB_GPIO_DAT_L, 0x00, 0x00}, + + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00}, + {BRIDGE, M5602_XB_GPIO_DIR, 0x07, 0x00}, + {BRIDGE, M5602_XB_GPIO_DAT, 0x0b, 0x00}, + {BRIDGE, M5602_XB_GPIO_EN_H, 0x06, 0x00}, + {BRIDGE, M5602_XB_GPIO_EN_L, 0x00, 0x00}, + + {BRIDGE, M5602_XB_I2C_CLK_DIV, 0x0a, 0x00} +}; + +static const unsigned char init_mt9m111[][4] = { + {BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02, 0x00}, + {BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00}, + {BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00}, + {BRIDGE, M5602_XB_SENSOR_TYPE, 0x09, 0x00}, + + {BRIDGE, M5602_XB_GPIO_EN_H, 0x06, 0x00}, + {BRIDGE, M5602_XB_GPIO_EN_L, 0x00, 0x00}, + {BRIDGE, M5602_XB_GPIO_DAT, 0x04, 0x00}, + {BRIDGE, M5602_XB_GPIO_DIR_H, 0x3e, 0x00}, + {BRIDGE, M5602_XB_GPIO_DIR_L, 0xff, 0x00}, + {BRIDGE, M5602_XB_GPIO_DAT_H, 0x02, 0x00}, + {BRIDGE, M5602_XB_GPIO_DAT_L, 0x00, 0x00}, + {BRIDGE, M5602_XB_GPIO_DIR, 0x07, 0x00}, + {BRIDGE, M5602_XB_GPIO_DAT, 0x0b, 0x00}, + {BRIDGE, M5602_XB_I2C_CLK_DIV, 0x0a, 0x00}, + + {SENSOR, MT9M111_SC_RESET, 0x00, 0x29}, + {SENSOR, MT9M111_PAGE_MAP, 0x00, 0x00}, + {SENSOR, MT9M111_SC_RESET, 0x00, 0x08}, + {SENSOR, MT9M111_PAGE_MAP, 0x00, 0x01}, + {SENSOR, MT9M111_CP_OPERATING_MODE_CTL, 0x00, + MT9M111_CP_OPERATING_MODE_CTL}, + {SENSOR, MT9M111_CP_LENS_CORRECTION_1, 0x04, 0x2a}, + {SENSOR, MT9M111_CP_DEFECT_CORR_CONTEXT_A, 0x00, + MT9M111_2D_DEFECT_CORRECTION_ENABLE}, + {SENSOR, MT9M111_CP_DEFECT_CORR_CONTEXT_B, 0x00, + MT9M111_2D_DEFECT_CORRECTION_ENABLE}, + {SENSOR, MT9M111_CP_LUMA_OFFSET, 0x00, 0x00}, + {SENSOR, MT9M111_CP_LUMA_CLIP, 0xff, 0x00}, + {SENSOR, MT9M111_CP_OUTPUT_FORMAT_CTL2_CONTEXT_A, 0x14, 0x00}, + {SENSOR, MT9M111_CP_OUTPUT_FORMAT_CTL2_CONTEXT_B, 0x14, 0x00}, + {SENSOR, 0xcd, 0x00, 0x0e}, + {SENSOR, 0xd0, 0x00, 0x40}, + + {SENSOR, MT9M111_PAGE_MAP, 0x00, 0x02}, + {SENSOR, MT9M111_CC_AUTO_EXPOSURE_PARAMETER_18, 0x00, 0x00}, + {SENSOR, MT9M111_CC_AWB_PARAMETER_7, 0xef, 0x03}, + + {SENSOR, MT9M111_PAGE_MAP, 0x00, 0x00}, + {SENSOR, 0x33, 0x03, 0x49}, + {SENSOR, 0x34, 0xc0, 0x19}, + {SENSOR, 0x3f, 0x20, 0x20}, + {SENSOR, 0x40, 0x20, 0x20}, + {SENSOR, 0x5a, 0xc0, 0x0a}, + {SENSOR, 0x70, 0x7b, 0x0a}, + {SENSOR, 0x71, 0xff, 0x00}, + {SENSOR, 0x72, 0x19, 0x0e}, + {SENSOR, 0x73, 0x18, 0x0f}, + {SENSOR, 0x74, 0x57, 0x32}, + {SENSOR, 0x75, 0x56, 0x34}, + {SENSOR, 0x76, 0x73, 0x35}, + {SENSOR, 0x77, 0x30, 0x12}, + {SENSOR, 0x78, 0x79, 0x02}, + {SENSOR, 0x79, 0x75, 0x06}, + {SENSOR, 0x7a, 0x77, 0x0a}, + {SENSOR, 0x7b, 0x78, 0x09}, + {SENSOR, 0x7c, 0x7d, 0x06}, + {SENSOR, 0x7d, 0x31, 0x10}, + {SENSOR, 0x7e, 0x00, 0x7e}, + {SENSOR, 0x80, 0x59, 0x04}, + {SENSOR, 0x81, 0x59, 0x04}, + {SENSOR, 0x82, 0x57, 0x0a}, + {SENSOR, 0x83, 0x58, 0x0b}, + {SENSOR, 0x84, 0x47, 0x0c}, + {SENSOR, 0x85, 0x48, 0x0e}, + {SENSOR, 0x86, 0x5b, 0x02}, + {SENSOR, 0x87, 0x00, 0x5c}, + {SENSOR, MT9M111_CONTEXT_CONTROL, 0x00, MT9M111_SEL_CONTEXT_B}, + {SENSOR, 0x60, 0x00, 0x80}, + {SENSOR, 0x61, 0x00, 0x00}, + {SENSOR, 0x62, 0x00, 0x00}, + {SENSOR, 0x63, 0x00, 0x00}, + {SENSOR, 0x64, 0x00, 0x00}, + + {SENSOR, MT9M111_SC_ROWSTART, 0x00, 0x0d}, /* 13 */ + {SENSOR, MT9M111_SC_COLSTART, 0x00, 0x12}, /* 18 */ + {SENSOR, MT9M111_SC_WINDOW_HEIGHT, 0x04, 0x00}, /* 1024 */ + {SENSOR, MT9M111_SC_WINDOW_WIDTH, 0x05, 0x10}, /* 1296 */ + {SENSOR, MT9M111_SC_HBLANK_CONTEXT_B, 0x01, 0x60}, /* 352 */ + {SENSOR, MT9M111_SC_VBLANK_CONTEXT_B, 0x00, 0x11}, /* 17 */ + {SENSOR, MT9M111_SC_HBLANK_CONTEXT_A, 0x01, 0x60}, /* 352 */ + {SENSOR, MT9M111_SC_VBLANK_CONTEXT_A, 0x00, 0x11}, /* 17 */ + {SENSOR, MT9M111_SC_R_MODE_CONTEXT_A, 0x01, 0x0f}, /* 271 */ + {SENSOR, 0x30, 0x04, 0x00}, + /* Set number of blank rows chosen to 400 */ + {SENSOR, MT9M111_SC_SHUTTER_WIDTH, 0x01, 0x90}, +}; + +static const unsigned char start_mt9m111[][4] = { + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x06, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00}, + {BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00}, + {BRIDGE, M5602_XB_SENSOR_TYPE, 0x09, 0x00}, + {BRIDGE, M5602_XB_LINE_OF_FRAME_H, 0x81, 0x00}, + {BRIDGE, M5602_XB_PIX_OF_LINE_H, 0x82, 0x00}, + {BRIDGE, M5602_XB_SIG_INI, 0x01, 0x00}, + {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, + {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, + {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, + {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, +}; + static struct v4l2_pix_format mt9m111_modes[] = { { 640, diff --git a/drivers/media/usb/gspca/m5602/m5602_mt9m111.h b/drivers/media/usb/gspca/m5602/m5602_mt9m111.h index 07448d35e3cd..781a16311822 100644 --- a/drivers/media/usb/gspca/m5602/m5602_mt9m111.h +++ b/drivers/media/usb/gspca/m5602/m5602_mt9m111.h @@ -126,148 +126,4 @@ static const struct m5602_sensor mt9m111 = { .disconnect = mt9m111_disconnect, .start = mt9m111_start, }; - -static const unsigned char preinit_mt9m111[][4] = { - {BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02, 0x00}, - {BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00}, - {BRIDGE, M5602_XB_SENSOR_TYPE, 0x0d, 0x00}, - {BRIDGE, M5602_XB_SENSOR_CTRL, 0x00, 0x00}, - {BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00}, - {BRIDGE, M5602_XB_SENSOR_TYPE, 0x09, 0x00}, - - {SENSOR, MT9M111_PAGE_MAP, 0x00, 0x00}, - {SENSOR, MT9M111_SC_RESET, - MT9M111_RESET | - MT9M111_RESTART | - MT9M111_ANALOG_STANDBY | - MT9M111_CHIP_DISABLE, - MT9M111_SHOW_BAD_FRAMES | - MT9M111_RESTART_BAD_FRAMES | - MT9M111_SYNCHRONIZE_CHANGES}, - - {BRIDGE, M5602_XB_GPIO_DIR, 0x05, 0x00}, - {BRIDGE, M5602_XB_GPIO_DAT, 0x04, 0x00}, - {BRIDGE, M5602_XB_GPIO_EN_H, 0x3e, 0x00}, - {BRIDGE, M5602_XB_GPIO_DIR_H, 0x3e, 0x00}, - {BRIDGE, M5602_XB_GPIO_DAT_H, 0x02, 0x00}, - {BRIDGE, M5602_XB_GPIO_EN_L, 0xff, 0x00}, - {BRIDGE, M5602_XB_GPIO_DIR_L, 0xff, 0x00}, - {BRIDGE, M5602_XB_GPIO_DAT_L, 0x00, 0x00}, - - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00}, - {BRIDGE, M5602_XB_GPIO_DIR, 0x07, 0x00}, - {BRIDGE, M5602_XB_GPIO_DAT, 0x0b, 0x00}, - {BRIDGE, M5602_XB_GPIO_EN_H, 0x06, 0x00}, - {BRIDGE, M5602_XB_GPIO_EN_L, 0x00, 0x00}, - - {BRIDGE, M5602_XB_I2C_CLK_DIV, 0x0a, 0x00} -}; - -static const unsigned char init_mt9m111[][4] = { - {BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02, 0x00}, - {BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00}, - {BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00}, - {BRIDGE, M5602_XB_SENSOR_TYPE, 0x09, 0x00}, - - {BRIDGE, M5602_XB_GPIO_EN_H, 0x06, 0x00}, - {BRIDGE, M5602_XB_GPIO_EN_L, 0x00, 0x00}, - {BRIDGE, M5602_XB_GPIO_DAT, 0x04, 0x00}, - {BRIDGE, M5602_XB_GPIO_DIR_H, 0x3e, 0x00}, - {BRIDGE, M5602_XB_GPIO_DIR_L, 0xff, 0x00}, - {BRIDGE, M5602_XB_GPIO_DAT_H, 0x02, 0x00}, - {BRIDGE, M5602_XB_GPIO_DAT_L, 0x00, 0x00}, - {BRIDGE, M5602_XB_GPIO_DIR, 0x07, 0x00}, - {BRIDGE, M5602_XB_GPIO_DAT, 0x0b, 0x00}, - {BRIDGE, M5602_XB_I2C_CLK_DIV, 0x0a, 0x00}, - - {SENSOR, MT9M111_SC_RESET, 0x00, 0x29}, - {SENSOR, MT9M111_PAGE_MAP, 0x00, 0x00}, - {SENSOR, MT9M111_SC_RESET, 0x00, 0x08}, - {SENSOR, MT9M111_PAGE_MAP, 0x00, 0x01}, - {SENSOR, MT9M111_CP_OPERATING_MODE_CTL, 0x00, - MT9M111_CP_OPERATING_MODE_CTL}, - {SENSOR, MT9M111_CP_LENS_CORRECTION_1, 0x04, 0x2a}, - {SENSOR, MT9M111_CP_DEFECT_CORR_CONTEXT_A, 0x00, - MT9M111_2D_DEFECT_CORRECTION_ENABLE}, - {SENSOR, MT9M111_CP_DEFECT_CORR_CONTEXT_B, 0x00, - MT9M111_2D_DEFECT_CORRECTION_ENABLE}, - {SENSOR, MT9M111_CP_LUMA_OFFSET, 0x00, 0x00}, - {SENSOR, MT9M111_CP_LUMA_CLIP, 0xff, 0x00}, - {SENSOR, MT9M111_CP_OUTPUT_FORMAT_CTL2_CONTEXT_A, 0x14, 0x00}, - {SENSOR, MT9M111_CP_OUTPUT_FORMAT_CTL2_CONTEXT_B, 0x14, 0x00}, - {SENSOR, 0xcd, 0x00, 0x0e}, - {SENSOR, 0xd0, 0x00, 0x40}, - - {SENSOR, MT9M111_PAGE_MAP, 0x00, 0x02}, - {SENSOR, MT9M111_CC_AUTO_EXPOSURE_PARAMETER_18, 0x00, 0x00}, - {SENSOR, MT9M111_CC_AWB_PARAMETER_7, 0xef, 0x03}, - - {SENSOR, MT9M111_PAGE_MAP, 0x00, 0x00}, - {SENSOR, 0x33, 0x03, 0x49}, - {SENSOR, 0x34, 0xc0, 0x19}, - {SENSOR, 0x3f, 0x20, 0x20}, - {SENSOR, 0x40, 0x20, 0x20}, - {SENSOR, 0x5a, 0xc0, 0x0a}, - {SENSOR, 0x70, 0x7b, 0x0a}, - {SENSOR, 0x71, 0xff, 0x00}, - {SENSOR, 0x72, 0x19, 0x0e}, - {SENSOR, 0x73, 0x18, 0x0f}, - {SENSOR, 0x74, 0x57, 0x32}, - {SENSOR, 0x75, 0x56, 0x34}, - {SENSOR, 0x76, 0x73, 0x35}, - {SENSOR, 0x77, 0x30, 0x12}, - {SENSOR, 0x78, 0x79, 0x02}, - {SENSOR, 0x79, 0x75, 0x06}, - {SENSOR, 0x7a, 0x77, 0x0a}, - {SENSOR, 0x7b, 0x78, 0x09}, - {SENSOR, 0x7c, 0x7d, 0x06}, - {SENSOR, 0x7d, 0x31, 0x10}, - {SENSOR, 0x7e, 0x00, 0x7e}, - {SENSOR, 0x80, 0x59, 0x04}, - {SENSOR, 0x81, 0x59, 0x04}, - {SENSOR, 0x82, 0x57, 0x0a}, - {SENSOR, 0x83, 0x58, 0x0b}, - {SENSOR, 0x84, 0x47, 0x0c}, - {SENSOR, 0x85, 0x48, 0x0e}, - {SENSOR, 0x86, 0x5b, 0x02}, - {SENSOR, 0x87, 0x00, 0x5c}, - {SENSOR, MT9M111_CONTEXT_CONTROL, 0x00, MT9M111_SEL_CONTEXT_B}, - {SENSOR, 0x60, 0x00, 0x80}, - {SENSOR, 0x61, 0x00, 0x00}, - {SENSOR, 0x62, 0x00, 0x00}, - {SENSOR, 0x63, 0x00, 0x00}, - {SENSOR, 0x64, 0x00, 0x00}, - - {SENSOR, MT9M111_SC_ROWSTART, 0x00, 0x0d}, /* 13 */ - {SENSOR, MT9M111_SC_COLSTART, 0x00, 0x12}, /* 18 */ - {SENSOR, MT9M111_SC_WINDOW_HEIGHT, 0x04, 0x00}, /* 1024 */ - {SENSOR, MT9M111_SC_WINDOW_WIDTH, 0x05, 0x10}, /* 1296 */ - {SENSOR, MT9M111_SC_HBLANK_CONTEXT_B, 0x01, 0x60}, /* 352 */ - {SENSOR, MT9M111_SC_VBLANK_CONTEXT_B, 0x00, 0x11}, /* 17 */ - {SENSOR, MT9M111_SC_HBLANK_CONTEXT_A, 0x01, 0x60}, /* 352 */ - {SENSOR, MT9M111_SC_VBLANK_CONTEXT_A, 0x00, 0x11}, /* 17 */ - {SENSOR, MT9M111_SC_R_MODE_CONTEXT_A, 0x01, 0x0f}, /* 271 */ - {SENSOR, 0x30, 0x04, 0x00}, - /* Set number of blank rows chosen to 400 */ - {SENSOR, MT9M111_SC_SHUTTER_WIDTH, 0x01, 0x90}, -}; - -static const unsigned char start_mt9m111[][4] = { - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x06, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00}, - {BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00}, - {BRIDGE, M5602_XB_SENSOR_TYPE, 0x09, 0x00}, - {BRIDGE, M5602_XB_LINE_OF_FRAME_H, 0x81, 0x00}, - {BRIDGE, M5602_XB_PIX_OF_LINE_H, 0x82, 0x00}, - {BRIDGE, M5602_XB_SIG_INI, 0x01, 0x00}, - {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, - {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, - {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, - {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, -}; #endif diff --git a/drivers/media/usb/gspca/m5602/m5602_ov7660.c b/drivers/media/usb/gspca/m5602/m5602_ov7660.c index 64b3b03a9141..672b7a520695 100644 --- a/drivers/media/usb/gspca/m5602/m5602_ov7660.c +++ b/drivers/media/usb/gspca/m5602/m5602_ov7660.c @@ -23,6 +23,159 @@ static int ov7660_s_ctrl(struct v4l2_ctrl *ctrl); static void ov7660_dump_registers(struct sd *sd); +static const unsigned char preinit_ov7660[][4] = { + {BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02}, + {BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0}, + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, + {BRIDGE, M5602_XB_ADC_CTRL, 0xc0}, + {BRIDGE, M5602_XB_SENSOR_TYPE, 0x0d}, + {BRIDGE, M5602_XB_SENSOR_CTRL, 0x00}, + {BRIDGE, M5602_XB_GPIO_DIR, 0x03}, + {BRIDGE, M5602_XB_GPIO_DIR, 0x03}, + {BRIDGE, M5602_XB_ADC_CTRL, 0xc0}, + {BRIDGE, M5602_XB_SENSOR_TYPE, 0x0c}, + + {SENSOR, OV7660_OFON, 0x0c}, + {SENSOR, OV7660_COM2, 0x11}, + {SENSOR, OV7660_COM7, 0x05}, + + {BRIDGE, M5602_XB_GPIO_DIR, 0x01}, + {BRIDGE, M5602_XB_GPIO_DAT, 0x04}, + {BRIDGE, M5602_XB_GPIO_EN_H, 0x06}, + {BRIDGE, M5602_XB_GPIO_DIR_H, 0x06}, + {BRIDGE, M5602_XB_GPIO_DAT_H, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x08}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, + {BRIDGE, M5602_XB_ADC_CTRL, 0xc0}, + {BRIDGE, M5602_XB_SENSOR_TYPE, 0x0c}, + {BRIDGE, M5602_XB_GPIO_DIR, 0x05}, + {BRIDGE, M5602_XB_GPIO_DAT, 0x00}, + {BRIDGE, M5602_XB_GPIO_EN_H, 0x06}, + {BRIDGE, M5602_XB_GPIO_EN_L, 0x00} +}; + +static const unsigned char init_ov7660[][4] = { + {BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02}, + {BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0}, + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, + {BRIDGE, M5602_XB_ADC_CTRL, 0xc0}, + {BRIDGE, M5602_XB_SENSOR_TYPE, 0x0d}, + {BRIDGE, M5602_XB_SENSOR_CTRL, 0x00}, + {BRIDGE, M5602_XB_GPIO_DIR, 0x01}, + {BRIDGE, M5602_XB_GPIO_DIR, 0x01}, + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, + {BRIDGE, M5602_XB_ADC_CTRL, 0xc0}, + {BRIDGE, M5602_XB_SENSOR_TYPE, 0x0c}, + {BRIDGE, M5602_XB_GPIO_DIR, 0x05}, + {BRIDGE, M5602_XB_GPIO_DAT, 0x00}, + {BRIDGE, M5602_XB_GPIO_EN_H, 0x06}, + {BRIDGE, M5602_XB_GPIO_EN_L, 0x00}, + {SENSOR, OV7660_COM7, 0x80}, + {SENSOR, OV7660_CLKRC, 0x80}, + {SENSOR, OV7660_COM9, 0x4c}, + {SENSOR, OV7660_OFON, 0x43}, + {SENSOR, OV7660_COM12, 0x28}, + {SENSOR, OV7660_COM8, 0x00}, + {SENSOR, OV7660_COM10, 0x40}, + {SENSOR, OV7660_HSTART, 0x0c}, + {SENSOR, OV7660_HSTOP, 0x61}, + {SENSOR, OV7660_HREF, 0xa4}, + {SENSOR, OV7660_PSHFT, 0x0b}, + {SENSOR, OV7660_VSTART, 0x01}, + {SENSOR, OV7660_VSTOP, 0x7a}, + {SENSOR, OV7660_VSTOP, 0x00}, + {SENSOR, OV7660_COM7, 0x05}, + {SENSOR, OV7660_COM6, 0x42}, + {SENSOR, OV7660_BBIAS, 0x94}, + {SENSOR, OV7660_GbBIAS, 0x94}, + {SENSOR, OV7660_RSVD29, 0x94}, + {SENSOR, OV7660_RBIAS, 0x94}, + {SENSOR, OV7660_COM1, 0x00}, + {SENSOR, OV7660_AECH, 0x00}, + {SENSOR, OV7660_AECHH, 0x00}, + {SENSOR, OV7660_ADC, 0x05}, + {SENSOR, OV7660_COM13, 0x00}, + {SENSOR, OV7660_RSVDA1, 0x23}, + {SENSOR, OV7660_TSLB, 0x0d}, + {SENSOR, OV7660_HV, 0x80}, + {SENSOR, OV7660_LCC1, 0x00}, + {SENSOR, OV7660_LCC2, 0x00}, + {SENSOR, OV7660_LCC3, 0x10}, + {SENSOR, OV7660_LCC4, 0x40}, + {SENSOR, OV7660_LCC5, 0x01}, + + {SENSOR, OV7660_AECH, 0x20}, + {SENSOR, OV7660_COM1, 0x00}, + {SENSOR, OV7660_OFON, 0x0c}, + {SENSOR, OV7660_COM2, 0x11}, + {SENSOR, OV7660_COM7, 0x05}, + {BRIDGE, M5602_XB_GPIO_DIR, 0x01}, + {BRIDGE, M5602_XB_GPIO_DAT, 0x04}, + {BRIDGE, M5602_XB_GPIO_EN_H, 0x06}, + {BRIDGE, M5602_XB_GPIO_DIR_H, 0x06}, + {BRIDGE, M5602_XB_GPIO_DAT_H, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x08}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, + {BRIDGE, M5602_XB_ADC_CTRL, 0xc0}, + {BRIDGE, M5602_XB_SENSOR_TYPE, 0x0c}, + {BRIDGE, M5602_XB_GPIO_DIR, 0x05}, + {BRIDGE, M5602_XB_GPIO_DAT, 0x00}, + {BRIDGE, M5602_XB_GPIO_EN_H, 0x06}, + {BRIDGE, M5602_XB_GPIO_EN_L, 0x00}, + {SENSOR, OV7660_AECH, 0x5f}, + {SENSOR, OV7660_COM1, 0x03}, + {SENSOR, OV7660_OFON, 0x0c}, + {SENSOR, OV7660_COM2, 0x11}, + {SENSOR, OV7660_COM7, 0x05}, + {BRIDGE, M5602_XB_GPIO_DIR, 0x01}, + {BRIDGE, M5602_XB_GPIO_DAT, 0x04}, + {BRIDGE, M5602_XB_GPIO_EN_H, 0x06}, + {BRIDGE, M5602_XB_GPIO_DIR_H, 0x06}, + {BRIDGE, M5602_XB_GPIO_DAT_H, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x08}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, + {BRIDGE, M5602_XB_ADC_CTRL, 0xc0}, + {BRIDGE, M5602_XB_SENSOR_TYPE, 0x0c}, + {BRIDGE, M5602_XB_GPIO_DIR, 0x05}, + {BRIDGE, M5602_XB_GPIO_DAT, 0x00}, + {BRIDGE, M5602_XB_GPIO_EN_H, 0x06}, + {BRIDGE, M5602_XB_GPIO_EN_L, 0x00}, + + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x06}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, + {BRIDGE, M5602_XB_ADC_CTRL, 0xc0}, + {BRIDGE, M5602_XB_SENSOR_TYPE, 0x0c}, + {BRIDGE, M5602_XB_LINE_OF_FRAME_H, 0x81}, + {BRIDGE, M5602_XB_PIX_OF_LINE_H, 0x82}, + {BRIDGE, M5602_XB_SIG_INI, 0x01}, + {BRIDGE, M5602_XB_VSYNC_PARA, 0x00}, + {BRIDGE, M5602_XB_VSYNC_PARA, 0x08}, + {BRIDGE, M5602_XB_VSYNC_PARA, 0x00}, + {BRIDGE, M5602_XB_VSYNC_PARA, 0x00}, + {BRIDGE, M5602_XB_VSYNC_PARA, 0x01}, + {BRIDGE, M5602_XB_VSYNC_PARA, 0xec}, + {BRIDGE, M5602_XB_VSYNC_PARA, 0x00}, + {BRIDGE, M5602_XB_VSYNC_PARA, 0x00}, + {BRIDGE, M5602_XB_SIG_INI, 0x00}, + {BRIDGE, M5602_XB_SIG_INI, 0x02}, + {BRIDGE, M5602_XB_HSYNC_PARA, 0x00}, + {BRIDGE, M5602_XB_HSYNC_PARA, 0x27}, + {BRIDGE, M5602_XB_HSYNC_PARA, 0x02}, + {BRIDGE, M5602_XB_HSYNC_PARA, 0xa7}, + {BRIDGE, M5602_XB_SIG_INI, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, +}; + static struct v4l2_pix_format ov7660_modes[] = { { 640, diff --git a/drivers/media/usb/gspca/m5602/m5602_ov7660.h b/drivers/media/usb/gspca/m5602/m5602_ov7660.h index 6fece1ce1232..72445d5df195 100644 --- a/drivers/media/usb/gspca/m5602/m5602_ov7660.h +++ b/drivers/media/usb/gspca/m5602/m5602_ov7660.h @@ -107,157 +107,4 @@ static const struct m5602_sensor ov7660 = { .stop = ov7660_stop, .disconnect = ov7660_disconnect, }; - -static const unsigned char preinit_ov7660[][4] = { - {BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02}, - {BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0}, - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, - {BRIDGE, M5602_XB_ADC_CTRL, 0xc0}, - {BRIDGE, M5602_XB_SENSOR_TYPE, 0x0d}, - {BRIDGE, M5602_XB_SENSOR_CTRL, 0x00}, - {BRIDGE, M5602_XB_GPIO_DIR, 0x03}, - {BRIDGE, M5602_XB_GPIO_DIR, 0x03}, - {BRIDGE, M5602_XB_ADC_CTRL, 0xc0}, - {BRIDGE, M5602_XB_SENSOR_TYPE, 0x0c}, - - {SENSOR, OV7660_OFON, 0x0c}, - {SENSOR, OV7660_COM2, 0x11}, - {SENSOR, OV7660_COM7, 0x05}, - - {BRIDGE, M5602_XB_GPIO_DIR, 0x01}, - {BRIDGE, M5602_XB_GPIO_DAT, 0x04}, - {BRIDGE, M5602_XB_GPIO_EN_H, 0x06}, - {BRIDGE, M5602_XB_GPIO_DIR_H, 0x06}, - {BRIDGE, M5602_XB_GPIO_DAT_H, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x08}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, - {BRIDGE, M5602_XB_ADC_CTRL, 0xc0}, - {BRIDGE, M5602_XB_SENSOR_TYPE, 0x0c}, - {BRIDGE, M5602_XB_GPIO_DIR, 0x05}, - {BRIDGE, M5602_XB_GPIO_DAT, 0x00}, - {BRIDGE, M5602_XB_GPIO_EN_H, 0x06}, - {BRIDGE, M5602_XB_GPIO_EN_L, 0x00} -}; - -static const unsigned char init_ov7660[][4] = { - {BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02}, - {BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0}, - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, - {BRIDGE, M5602_XB_ADC_CTRL, 0xc0}, - {BRIDGE, M5602_XB_SENSOR_TYPE, 0x0d}, - {BRIDGE, M5602_XB_SENSOR_CTRL, 0x00}, - {BRIDGE, M5602_XB_GPIO_DIR, 0x01}, - {BRIDGE, M5602_XB_GPIO_DIR, 0x01}, - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, - {BRIDGE, M5602_XB_ADC_CTRL, 0xc0}, - {BRIDGE, M5602_XB_SENSOR_TYPE, 0x0c}, - {BRIDGE, M5602_XB_GPIO_DIR, 0x05}, - {BRIDGE, M5602_XB_GPIO_DAT, 0x00}, - {BRIDGE, M5602_XB_GPIO_EN_H, 0x06}, - {BRIDGE, M5602_XB_GPIO_EN_L, 0x00}, - {SENSOR, OV7660_COM7, 0x80}, - {SENSOR, OV7660_CLKRC, 0x80}, - {SENSOR, OV7660_COM9, 0x4c}, - {SENSOR, OV7660_OFON, 0x43}, - {SENSOR, OV7660_COM12, 0x28}, - {SENSOR, OV7660_COM8, 0x00}, - {SENSOR, OV7660_COM10, 0x40}, - {SENSOR, OV7660_HSTART, 0x0c}, - {SENSOR, OV7660_HSTOP, 0x61}, - {SENSOR, OV7660_HREF, 0xa4}, - {SENSOR, OV7660_PSHFT, 0x0b}, - {SENSOR, OV7660_VSTART, 0x01}, - {SENSOR, OV7660_VSTOP, 0x7a}, - {SENSOR, OV7660_VSTOP, 0x00}, - {SENSOR, OV7660_COM7, 0x05}, - {SENSOR, OV7660_COM6, 0x42}, - {SENSOR, OV7660_BBIAS, 0x94}, - {SENSOR, OV7660_GbBIAS, 0x94}, - {SENSOR, OV7660_RSVD29, 0x94}, - {SENSOR, OV7660_RBIAS, 0x94}, - {SENSOR, OV7660_COM1, 0x00}, - {SENSOR, OV7660_AECH, 0x00}, - {SENSOR, OV7660_AECHH, 0x00}, - {SENSOR, OV7660_ADC, 0x05}, - {SENSOR, OV7660_COM13, 0x00}, - {SENSOR, OV7660_RSVDA1, 0x23}, - {SENSOR, OV7660_TSLB, 0x0d}, - {SENSOR, OV7660_HV, 0x80}, - {SENSOR, OV7660_LCC1, 0x00}, - {SENSOR, OV7660_LCC2, 0x00}, - {SENSOR, OV7660_LCC3, 0x10}, - {SENSOR, OV7660_LCC4, 0x40}, - {SENSOR, OV7660_LCC5, 0x01}, - - {SENSOR, OV7660_AECH, 0x20}, - {SENSOR, OV7660_COM1, 0x00}, - {SENSOR, OV7660_OFON, 0x0c}, - {SENSOR, OV7660_COM2, 0x11}, - {SENSOR, OV7660_COM7, 0x05}, - {BRIDGE, M5602_XB_GPIO_DIR, 0x01}, - {BRIDGE, M5602_XB_GPIO_DAT, 0x04}, - {BRIDGE, M5602_XB_GPIO_EN_H, 0x06}, - {BRIDGE, M5602_XB_GPIO_DIR_H, 0x06}, - {BRIDGE, M5602_XB_GPIO_DAT_H, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x08}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, - {BRIDGE, M5602_XB_ADC_CTRL, 0xc0}, - {BRIDGE, M5602_XB_SENSOR_TYPE, 0x0c}, - {BRIDGE, M5602_XB_GPIO_DIR, 0x05}, - {BRIDGE, M5602_XB_GPIO_DAT, 0x00}, - {BRIDGE, M5602_XB_GPIO_EN_H, 0x06}, - {BRIDGE, M5602_XB_GPIO_EN_L, 0x00}, - {SENSOR, OV7660_AECH, 0x5f}, - {SENSOR, OV7660_COM1, 0x03}, - {SENSOR, OV7660_OFON, 0x0c}, - {SENSOR, OV7660_COM2, 0x11}, - {SENSOR, OV7660_COM7, 0x05}, - {BRIDGE, M5602_XB_GPIO_DIR, 0x01}, - {BRIDGE, M5602_XB_GPIO_DAT, 0x04}, - {BRIDGE, M5602_XB_GPIO_EN_H, 0x06}, - {BRIDGE, M5602_XB_GPIO_DIR_H, 0x06}, - {BRIDGE, M5602_XB_GPIO_DAT_H, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x08}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, - {BRIDGE, M5602_XB_ADC_CTRL, 0xc0}, - {BRIDGE, M5602_XB_SENSOR_TYPE, 0x0c}, - {BRIDGE, M5602_XB_GPIO_DIR, 0x05}, - {BRIDGE, M5602_XB_GPIO_DAT, 0x00}, - {BRIDGE, M5602_XB_GPIO_EN_H, 0x06}, - {BRIDGE, M5602_XB_GPIO_EN_L, 0x00}, - - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x06}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, - {BRIDGE, M5602_XB_ADC_CTRL, 0xc0}, - {BRIDGE, M5602_XB_SENSOR_TYPE, 0x0c}, - {BRIDGE, M5602_XB_LINE_OF_FRAME_H, 0x81}, - {BRIDGE, M5602_XB_PIX_OF_LINE_H, 0x82}, - {BRIDGE, M5602_XB_SIG_INI, 0x01}, - {BRIDGE, M5602_XB_VSYNC_PARA, 0x00}, - {BRIDGE, M5602_XB_VSYNC_PARA, 0x08}, - {BRIDGE, M5602_XB_VSYNC_PARA, 0x00}, - {BRIDGE, M5602_XB_VSYNC_PARA, 0x00}, - {BRIDGE, M5602_XB_VSYNC_PARA, 0x01}, - {BRIDGE, M5602_XB_VSYNC_PARA, 0xec}, - {BRIDGE, M5602_XB_VSYNC_PARA, 0x00}, - {BRIDGE, M5602_XB_VSYNC_PARA, 0x00}, - {BRIDGE, M5602_XB_SIG_INI, 0x00}, - {BRIDGE, M5602_XB_SIG_INI, 0x02}, - {BRIDGE, M5602_XB_HSYNC_PARA, 0x00}, - {BRIDGE, M5602_XB_HSYNC_PARA, 0x27}, - {BRIDGE, M5602_XB_HSYNC_PARA, 0x02}, - {BRIDGE, M5602_XB_HSYNC_PARA, 0xa7}, - {BRIDGE, M5602_XB_SIG_INI, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, -}; #endif diff --git a/drivers/media/usb/gspca/m5602/m5602_ov9650.c b/drivers/media/usb/gspca/m5602/m5602_ov9650.c index 59bc62bfae26..4544d3a1ad58 100644 --- a/drivers/media/usb/gspca/m5602/m5602_ov9650.c +++ b/drivers/media/usb/gspca/m5602/m5602_ov9650.c @@ -1,3 +1,4 @@ + /* * Driver for the ov9650 sensor * @@ -23,6 +24,157 @@ static int ov9650_s_ctrl(struct v4l2_ctrl *ctrl); static void ov9650_dump_registers(struct sd *sd); +static const unsigned char preinit_ov9650[][3] = { + /* [INITCAM] */ + {BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02}, + {BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0}, + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, + {BRIDGE, M5602_XB_ADC_CTRL, 0xc0}, + {BRIDGE, M5602_XB_SENSOR_CTRL, 0x00}, + + {BRIDGE, M5602_XB_SENSOR_TYPE, 0x08}, + {BRIDGE, M5602_XB_GPIO_DIR, 0x05}, + {BRIDGE, M5602_XB_GPIO_DAT, 0x04}, + {BRIDGE, M5602_XB_GPIO_EN_H, 0x06}, + {BRIDGE, M5602_XB_GPIO_DIR_H, 0x06}, + {BRIDGE, M5602_XB_GPIO_DAT_H, 0x00}, + {BRIDGE, M5602_XB_GPIO_DAT, 0x00}, + {BRIDGE, M5602_XB_I2C_CLK_DIV, 0x0a}, + /* Reset chip */ + {SENSOR, OV9650_COM7, OV9650_REGISTER_RESET}, + /* Enable double clock */ + {SENSOR, OV9650_CLKRC, 0x80}, + /* Do something out of spec with the power */ + {SENSOR, OV9650_OFON, 0x40} +}; + +static const unsigned char init_ov9650[][3] = { + /* [INITCAM] */ + {BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02}, + {BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0}, + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, + {BRIDGE, M5602_XB_ADC_CTRL, 0xc0}, + {BRIDGE, M5602_XB_SENSOR_CTRL, 0x00}, + + {BRIDGE, M5602_XB_SENSOR_TYPE, 0x08}, + {BRIDGE, M5602_XB_GPIO_DIR, 0x05}, + {BRIDGE, M5602_XB_GPIO_DAT, 0x04}, + {BRIDGE, M5602_XB_GPIO_EN_H, 0x06}, + {BRIDGE, M5602_XB_GPIO_DIR_H, 0x06}, + {BRIDGE, M5602_XB_GPIO_DAT_H, 0x00}, + {BRIDGE, M5602_XB_GPIO_DAT, 0x00}, + {BRIDGE, M5602_XB_I2C_CLK_DIV, 0x0a}, + + /* Reset chip */ + {SENSOR, OV9650_COM7, OV9650_REGISTER_RESET}, + /* One extra reset is needed in order to make the sensor behave + properly when resuming from ram, could be a timing issue */ + {SENSOR, OV9650_COM7, OV9650_REGISTER_RESET}, + + /* Enable double clock */ + {SENSOR, OV9650_CLKRC, 0x80}, + /* Do something out of spec with the power */ + {SENSOR, OV9650_OFON, 0x40}, + + /* Set fast AGC/AEC algorithm with unlimited step size */ + {SENSOR, OV9650_COM8, OV9650_FAST_AGC_AEC | + OV9650_AEC_UNLIM_STEP_SIZE}, + + {SENSOR, OV9650_CHLF, 0x10}, + {SENSOR, OV9650_ARBLM, 0xbf}, + {SENSOR, OV9650_ACOM38, 0x81}, + /* Turn off color matrix coefficient double option */ + {SENSOR, OV9650_COM16, 0x00}, + /* Enable color matrix for RGB/YUV, Delay Y channel, + set output Y/UV delay to 1 */ + {SENSOR, OV9650_COM13, 0x19}, + /* Enable digital BLC, Set output mode to U Y V Y */ + {SENSOR, OV9650_TSLB, 0x0c}, + /* Limit the AGC/AEC stable upper region */ + {SENSOR, OV9650_COM24, 0x00}, + /* Enable HREF and some out of spec things */ + {SENSOR, OV9650_COM12, 0x73}, + /* Set all DBLC offset signs to positive and + do some out of spec stuff */ + {SENSOR, OV9650_DBLC1, 0xdf}, + {SENSOR, OV9650_COM21, 0x06}, + {SENSOR, OV9650_RSVD35, 0x91}, + /* Necessary, no camera stream without it */ + {SENSOR, OV9650_RSVD16, 0x06}, + {SENSOR, OV9650_RSVD94, 0x99}, + {SENSOR, OV9650_RSVD95, 0x99}, + {SENSOR, OV9650_RSVD96, 0x04}, + /* Enable full range output */ + {SENSOR, OV9650_COM15, 0x0}, + /* Enable HREF at optical black, enable ADBLC bias, + enable ADBLC, reset timings at format change */ + {SENSOR, OV9650_COM6, 0x4b}, + /* Subtract 32 from the B channel bias */ + {SENSOR, OV9650_BBIAS, 0xa0}, + /* Subtract 32 from the Gb channel bias */ + {SENSOR, OV9650_GbBIAS, 0xa0}, + /* Do not bypass the analog BLC and to some out of spec stuff */ + {SENSOR, OV9650_Gr_COM, 0x00}, + /* Subtract 32 from the R channel bias */ + {SENSOR, OV9650_RBIAS, 0xa0}, + /* Subtract 32 from the R channel bias */ + {SENSOR, OV9650_RBIAS, 0x0}, + {SENSOR, OV9650_COM26, 0x80}, + {SENSOR, OV9650_ACOMA9, 0x98}, + /* Set the AGC/AEC stable region upper limit */ + {SENSOR, OV9650_AEW, 0x68}, + /* Set the AGC/AEC stable region lower limit */ + {SENSOR, OV9650_AEB, 0x5c}, + /* Set the high and low limit nibbles to 3 */ + {SENSOR, OV9650_VPT, 0xc3}, + /* Set the Automatic Gain Ceiling (AGC) to 128x, + drop VSYNC at frame drop, + limit exposure timing, + drop frame when the AEC step is larger than the exposure gap */ + {SENSOR, OV9650_COM9, 0x6e}, + /* Set VSYNC negative, Set RESET to SLHS (slave mode horizontal sync) + and set PWDN to SLVS (slave mode vertical sync) */ + {SENSOR, OV9650_COM10, 0x42}, + /* Set horizontal column start high to default value */ + {SENSOR, OV9650_HSTART, 0x1a}, /* 210 */ + /* Set horizontal column end */ + {SENSOR, OV9650_HSTOP, 0xbf}, /* 1534 */ + /* Complementing register to the two writes above */ + {SENSOR, OV9650_HREF, 0xb2}, + /* Set vertical row start high bits */ + {SENSOR, OV9650_VSTRT, 0x02}, + /* Set vertical row end low bits */ + {SENSOR, OV9650_VSTOP, 0x7e}, + /* Set complementing vertical frame control */ + {SENSOR, OV9650_VREF, 0x10}, + {SENSOR, OV9650_ADC, 0x04}, + {SENSOR, OV9650_HV, 0x40}, + + /* Enable denoise, and white-pixel erase */ + {SENSOR, OV9650_COM22, OV9650_DENOISE_ENABLE | + OV9650_WHITE_PIXEL_ENABLE | + OV9650_WHITE_PIXEL_OPTION}, + + /* Enable VARIOPIXEL */ + {SENSOR, OV9650_COM3, OV9650_VARIOPIXEL}, + {SENSOR, OV9650_COM4, OV9650_QVGA_VARIOPIXEL}, + + /* Put the sensor in soft sleep mode */ + {SENSOR, OV9650_COM2, OV9650_SOFT_SLEEP | OV9650_OUTPUT_DRIVE_2X}, +}; + +static const unsigned char res_init_ov9650[][3] = { + {SENSOR, OV9650_COM2, OV9650_OUTPUT_DRIVE_2X}, + + {BRIDGE, M5602_XB_LINE_OF_FRAME_H, 0x82}, + {BRIDGE, M5602_XB_LINE_OF_FRAME_L, 0x00}, + {BRIDGE, M5602_XB_PIX_OF_LINE_H, 0x82}, + {BRIDGE, M5602_XB_PIX_OF_LINE_L, 0x00}, + {BRIDGE, M5602_XB_SIG_INI, 0x01} +}; + /* Vertically and horizontally flips the image if matched, needed for machines where the sensor is mounted upside down */ static diff --git a/drivers/media/usb/gspca/m5602/m5602_ov9650.h b/drivers/media/usb/gspca/m5602/m5602_ov9650.h index f9f5870da60f..ce3db062c740 100644 --- a/drivers/media/usb/gspca/m5602/m5602_ov9650.h +++ b/drivers/media/usb/gspca/m5602/m5602_ov9650.h @@ -156,154 +156,4 @@ static const struct m5602_sensor ov9650 = { .disconnect = ov9650_disconnect, }; -static const unsigned char preinit_ov9650[][3] = { - /* [INITCAM] */ - {BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02}, - {BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0}, - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, - {BRIDGE, M5602_XB_ADC_CTRL, 0xc0}, - {BRIDGE, M5602_XB_SENSOR_CTRL, 0x00}, - - {BRIDGE, M5602_XB_SENSOR_TYPE, 0x08}, - {BRIDGE, M5602_XB_GPIO_DIR, 0x05}, - {BRIDGE, M5602_XB_GPIO_DAT, 0x04}, - {BRIDGE, M5602_XB_GPIO_EN_H, 0x06}, - {BRIDGE, M5602_XB_GPIO_DIR_H, 0x06}, - {BRIDGE, M5602_XB_GPIO_DAT_H, 0x00}, - {BRIDGE, M5602_XB_GPIO_DAT, 0x00}, - {BRIDGE, M5602_XB_I2C_CLK_DIV, 0x0a}, - /* Reset chip */ - {SENSOR, OV9650_COM7, OV9650_REGISTER_RESET}, - /* Enable double clock */ - {SENSOR, OV9650_CLKRC, 0x80}, - /* Do something out of spec with the power */ - {SENSOR, OV9650_OFON, 0x40} -}; - -static const unsigned char init_ov9650[][3] = { - /* [INITCAM] */ - {BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02}, - {BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0}, - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, - {BRIDGE, M5602_XB_ADC_CTRL, 0xc0}, - {BRIDGE, M5602_XB_SENSOR_CTRL, 0x00}, - - {BRIDGE, M5602_XB_SENSOR_TYPE, 0x08}, - {BRIDGE, M5602_XB_GPIO_DIR, 0x05}, - {BRIDGE, M5602_XB_GPIO_DAT, 0x04}, - {BRIDGE, M5602_XB_GPIO_EN_H, 0x06}, - {BRIDGE, M5602_XB_GPIO_DIR_H, 0x06}, - {BRIDGE, M5602_XB_GPIO_DAT_H, 0x00}, - {BRIDGE, M5602_XB_GPIO_DAT, 0x00}, - {BRIDGE, M5602_XB_I2C_CLK_DIV, 0x0a}, - - /* Reset chip */ - {SENSOR, OV9650_COM7, OV9650_REGISTER_RESET}, - /* One extra reset is needed in order to make the sensor behave - properly when resuming from ram, could be a timing issue */ - {SENSOR, OV9650_COM7, OV9650_REGISTER_RESET}, - - /* Enable double clock */ - {SENSOR, OV9650_CLKRC, 0x80}, - /* Do something out of spec with the power */ - {SENSOR, OV9650_OFON, 0x40}, - - /* Set fast AGC/AEC algorithm with unlimited step size */ - {SENSOR, OV9650_COM8, OV9650_FAST_AGC_AEC | - OV9650_AEC_UNLIM_STEP_SIZE}, - - {SENSOR, OV9650_CHLF, 0x10}, - {SENSOR, OV9650_ARBLM, 0xbf}, - {SENSOR, OV9650_ACOM38, 0x81}, - /* Turn off color matrix coefficient double option */ - {SENSOR, OV9650_COM16, 0x00}, - /* Enable color matrix for RGB/YUV, Delay Y channel, - set output Y/UV delay to 1 */ - {SENSOR, OV9650_COM13, 0x19}, - /* Enable digital BLC, Set output mode to U Y V Y */ - {SENSOR, OV9650_TSLB, 0x0c}, - /* Limit the AGC/AEC stable upper region */ - {SENSOR, OV9650_COM24, 0x00}, - /* Enable HREF and some out of spec things */ - {SENSOR, OV9650_COM12, 0x73}, - /* Set all DBLC offset signs to positive and - do some out of spec stuff */ - {SENSOR, OV9650_DBLC1, 0xdf}, - {SENSOR, OV9650_COM21, 0x06}, - {SENSOR, OV9650_RSVD35, 0x91}, - /* Necessary, no camera stream without it */ - {SENSOR, OV9650_RSVD16, 0x06}, - {SENSOR, OV9650_RSVD94, 0x99}, - {SENSOR, OV9650_RSVD95, 0x99}, - {SENSOR, OV9650_RSVD96, 0x04}, - /* Enable full range output */ - {SENSOR, OV9650_COM15, 0x0}, - /* Enable HREF at optical black, enable ADBLC bias, - enable ADBLC, reset timings at format change */ - {SENSOR, OV9650_COM6, 0x4b}, - /* Subtract 32 from the B channel bias */ - {SENSOR, OV9650_BBIAS, 0xa0}, - /* Subtract 32 from the Gb channel bias */ - {SENSOR, OV9650_GbBIAS, 0xa0}, - /* Do not bypass the analog BLC and to some out of spec stuff */ - {SENSOR, OV9650_Gr_COM, 0x00}, - /* Subtract 32 from the R channel bias */ - {SENSOR, OV9650_RBIAS, 0xa0}, - /* Subtract 32 from the R channel bias */ - {SENSOR, OV9650_RBIAS, 0x0}, - {SENSOR, OV9650_COM26, 0x80}, - {SENSOR, OV9650_ACOMA9, 0x98}, - /* Set the AGC/AEC stable region upper limit */ - {SENSOR, OV9650_AEW, 0x68}, - /* Set the AGC/AEC stable region lower limit */ - {SENSOR, OV9650_AEB, 0x5c}, - /* Set the high and low limit nibbles to 3 */ - {SENSOR, OV9650_VPT, 0xc3}, - /* Set the Automatic Gain Ceiling (AGC) to 128x, - drop VSYNC at frame drop, - limit exposure timing, - drop frame when the AEC step is larger than the exposure gap */ - {SENSOR, OV9650_COM9, 0x6e}, - /* Set VSYNC negative, Set RESET to SLHS (slave mode horizontal sync) - and set PWDN to SLVS (slave mode vertical sync) */ - {SENSOR, OV9650_COM10, 0x42}, - /* Set horizontal column start high to default value */ - {SENSOR, OV9650_HSTART, 0x1a}, /* 210 */ - /* Set horizontal column end */ - {SENSOR, OV9650_HSTOP, 0xbf}, /* 1534 */ - /* Complementing register to the two writes above */ - {SENSOR, OV9650_HREF, 0xb2}, - /* Set vertical row start high bits */ - {SENSOR, OV9650_VSTRT, 0x02}, - /* Set vertical row end low bits */ - {SENSOR, OV9650_VSTOP, 0x7e}, - /* Set complementing vertical frame control */ - {SENSOR, OV9650_VREF, 0x10}, - {SENSOR, OV9650_ADC, 0x04}, - {SENSOR, OV9650_HV, 0x40}, - - /* Enable denoise, and white-pixel erase */ - {SENSOR, OV9650_COM22, OV9650_DENOISE_ENABLE | - OV9650_WHITE_PIXEL_ENABLE | - OV9650_WHITE_PIXEL_OPTION}, - - /* Enable VARIOPIXEL */ - {SENSOR, OV9650_COM3, OV9650_VARIOPIXEL}, - {SENSOR, OV9650_COM4, OV9650_QVGA_VARIOPIXEL}, - - /* Put the sensor in soft sleep mode */ - {SENSOR, OV9650_COM2, OV9650_SOFT_SLEEP | OV9650_OUTPUT_DRIVE_2X}, -}; - -static const unsigned char res_init_ov9650[][3] = { - {SENSOR, OV9650_COM2, OV9650_OUTPUT_DRIVE_2X}, - - {BRIDGE, M5602_XB_LINE_OF_FRAME_H, 0x82}, - {BRIDGE, M5602_XB_LINE_OF_FRAME_L, 0x00}, - {BRIDGE, M5602_XB_PIX_OF_LINE_H, 0x82}, - {BRIDGE, M5602_XB_PIX_OF_LINE_L, 0x00}, - {BRIDGE, M5602_XB_SIG_INI, 0x01} -}; #endif diff --git a/drivers/media/usb/gspca/m5602/m5602_po1030.c b/drivers/media/usb/gspca/m5602/m5602_po1030.c index 4bf5c43424b7..a0a90dd34ca8 100644 --- a/drivers/media/usb/gspca/m5602/m5602_po1030.c +++ b/drivers/media/usb/gspca/m5602/m5602_po1030.c @@ -23,6 +23,110 @@ static int po1030_s_ctrl(struct v4l2_ctrl *ctrl); static void po1030_dump_registers(struct sd *sd); +static const unsigned char preinit_po1030[][3] = { + {BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02}, + {BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0}, + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, + {BRIDGE, M5602_XB_ADC_CTRL, 0xc0}, + {BRIDGE, M5602_XB_SENSOR_CTRL, 0x00}, + {BRIDGE, M5602_XB_SENSOR_TYPE, 0x0c}, + {BRIDGE, M5602_XB_ADC_CTRL, 0xc0}, + {BRIDGE, M5602_XB_GPIO_DIR, 0x05}, + {BRIDGE, M5602_XB_GPIO_DAT, 0x04}, + {BRIDGE, M5602_XB_GPIO_EN_H, 0x06}, + {BRIDGE, M5602_XB_GPIO_DIR_H, 0x06}, + {BRIDGE, M5602_XB_GPIO_DAT_H, 0x02}, + + {SENSOR, PO1030_AUTOCTRL2, PO1030_SENSOR_RESET | (1 << 2)}, + + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x04}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, + {BRIDGE, M5602_XB_SENSOR_TYPE, 0x0c}, + {BRIDGE, M5602_XB_GPIO_DIR, 0x05}, + {BRIDGE, M5602_XB_GPIO_DAT, 0x00} +}; + +static const unsigned char init_po1030[][3] = { + {BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02}, + {BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0}, + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, + {BRIDGE, M5602_XB_ADC_CTRL, 0xc0}, + {BRIDGE, M5602_XB_SENSOR_CTRL, 0x00}, + {BRIDGE, M5602_XB_SENSOR_TYPE, 0x0c}, + + {SENSOR, PO1030_AUTOCTRL2, PO1030_SENSOR_RESET | (1 << 2)}, + + {BRIDGE, M5602_XB_GPIO_DIR, 0x05}, + {BRIDGE, M5602_XB_GPIO_DAT, 0x04}, + {BRIDGE, M5602_XB_GPIO_EN_H, 0x06}, + {BRIDGE, M5602_XB_GPIO_EN_L, 0x00}, + {BRIDGE, M5602_XB_GPIO_DIR_H, 0x06}, + {BRIDGE, M5602_XB_GPIO_DAT_H, 0x02}, + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x04}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, + {BRIDGE, M5602_XB_GPIO_DIR, 0x05}, + {BRIDGE, M5602_XB_GPIO_DAT, 0x00}, + + {SENSOR, PO1030_AUTOCTRL2, 0x04}, + + {SENSOR, PO1030_OUTFORMCTRL2, PO1030_RAW_RGB_BAYER}, + {SENSOR, PO1030_AUTOCTRL1, PO1030_WEIGHT_WIN_2X}, + + {SENSOR, PO1030_CONTROL2, 0x03}, + {SENSOR, 0x21, 0x90}, + {SENSOR, PO1030_YTARGET, 0x60}, + {SENSOR, 0x59, 0x13}, + {SENSOR, PO1030_OUTFORMCTRL1, PO1030_HREF_ENABLE}, + {SENSOR, PO1030_EDGE_ENH_OFF, 0x00}, + {SENSOR, PO1030_EGA, 0x80}, + {SENSOR, 0x78, 0x14}, + {SENSOR, 0x6f, 0x01}, + {SENSOR, PO1030_GLOBALGAINMAX, 0x14}, + {SENSOR, PO1030_Cb_U_GAIN, 0x38}, + {SENSOR, PO1030_Cr_V_GAIN, 0x38}, + {SENSOR, PO1030_CONTROL1, PO1030_SHUTTER_MODE | + PO1030_AUTO_SUBSAMPLING | + PO1030_FRAME_EQUAL}, + {SENSOR, PO1030_GC0, 0x10}, + {SENSOR, PO1030_GC1, 0x20}, + {SENSOR, PO1030_GC2, 0x40}, + {SENSOR, PO1030_GC3, 0x60}, + {SENSOR, PO1030_GC4, 0x80}, + {SENSOR, PO1030_GC5, 0xa0}, + {SENSOR, PO1030_GC6, 0xc0}, + {SENSOR, PO1030_GC7, 0xff}, + + /* Set the width to 751 */ + {SENSOR, PO1030_FRAMEWIDTH_H, 0x02}, + {SENSOR, PO1030_FRAMEWIDTH_L, 0xef}, + + /* Set the height to 540 */ + {SENSOR, PO1030_FRAMEHEIGHT_H, 0x02}, + {SENSOR, PO1030_FRAMEHEIGHT_L, 0x1c}, + + /* Set the x window to 1 */ + {SENSOR, PO1030_WINDOWX_H, 0x00}, + {SENSOR, PO1030_WINDOWX_L, 0x01}, + + /* Set the y window to 1 */ + {SENSOR, PO1030_WINDOWY_H, 0x00}, + {SENSOR, PO1030_WINDOWY_L, 0x01}, + + /* with a very low lighted environment increase the exposure but + * decrease the FPS (Frame Per Second) */ + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, + + {BRIDGE, M5602_XB_GPIO_DIR, 0x05}, + {BRIDGE, M5602_XB_GPIO_DAT, 0x00}, + {BRIDGE, M5602_XB_GPIO_EN_H, 0x06}, + {BRIDGE, M5602_XB_GPIO_EN_L, 0x00}, +}; + static struct v4l2_pix_format po1030_modes[] = { { 640, diff --git a/drivers/media/usb/gspca/m5602/m5602_po1030.h b/drivers/media/usb/gspca/m5602/m5602_po1030.h index a6ab76149bd0..981a91aa7450 100644 --- a/drivers/media/usb/gspca/m5602/m5602_po1030.h +++ b/drivers/media/usb/gspca/m5602/m5602_po1030.h @@ -167,108 +167,4 @@ static const struct m5602_sensor po1030 = { .start = po1030_start, .disconnect = po1030_disconnect, }; - -static const unsigned char preinit_po1030[][3] = { - {BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02}, - {BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0}, - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, - {BRIDGE, M5602_XB_ADC_CTRL, 0xc0}, - {BRIDGE, M5602_XB_SENSOR_CTRL, 0x00}, - {BRIDGE, M5602_XB_SENSOR_TYPE, 0x0c}, - {BRIDGE, M5602_XB_ADC_CTRL, 0xc0}, - {BRIDGE, M5602_XB_GPIO_DIR, 0x05}, - {BRIDGE, M5602_XB_GPIO_DAT, 0x04}, - {BRIDGE, M5602_XB_GPIO_EN_H, 0x06}, - {BRIDGE, M5602_XB_GPIO_DIR_H, 0x06}, - {BRIDGE, M5602_XB_GPIO_DAT_H, 0x02}, - - {SENSOR, PO1030_AUTOCTRL2, PO1030_SENSOR_RESET | (1 << 2)}, - - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x04}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, - {BRIDGE, M5602_XB_SENSOR_TYPE, 0x0c}, - {BRIDGE, M5602_XB_GPIO_DIR, 0x05}, - {BRIDGE, M5602_XB_GPIO_DAT, 0x00} -}; - -static const unsigned char init_po1030[][3] = { - {BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02}, - {BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0}, - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, - {BRIDGE, M5602_XB_ADC_CTRL, 0xc0}, - {BRIDGE, M5602_XB_SENSOR_CTRL, 0x00}, - {BRIDGE, M5602_XB_SENSOR_TYPE, 0x0c}, - - {SENSOR, PO1030_AUTOCTRL2, PO1030_SENSOR_RESET | (1 << 2)}, - - {BRIDGE, M5602_XB_GPIO_DIR, 0x05}, - {BRIDGE, M5602_XB_GPIO_DAT, 0x04}, - {BRIDGE, M5602_XB_GPIO_EN_H, 0x06}, - {BRIDGE, M5602_XB_GPIO_EN_L, 0x00}, - {BRIDGE, M5602_XB_GPIO_DIR_H, 0x06}, - {BRIDGE, M5602_XB_GPIO_DAT_H, 0x02}, - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x04}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, - {BRIDGE, M5602_XB_GPIO_DIR, 0x05}, - {BRIDGE, M5602_XB_GPIO_DAT, 0x00}, - - {SENSOR, PO1030_AUTOCTRL2, 0x04}, - - {SENSOR, PO1030_OUTFORMCTRL2, PO1030_RAW_RGB_BAYER}, - {SENSOR, PO1030_AUTOCTRL1, PO1030_WEIGHT_WIN_2X}, - - {SENSOR, PO1030_CONTROL2, 0x03}, - {SENSOR, 0x21, 0x90}, - {SENSOR, PO1030_YTARGET, 0x60}, - {SENSOR, 0x59, 0x13}, - {SENSOR, PO1030_OUTFORMCTRL1, PO1030_HREF_ENABLE}, - {SENSOR, PO1030_EDGE_ENH_OFF, 0x00}, - {SENSOR, PO1030_EGA, 0x80}, - {SENSOR, 0x78, 0x14}, - {SENSOR, 0x6f, 0x01}, - {SENSOR, PO1030_GLOBALGAINMAX, 0x14}, - {SENSOR, PO1030_Cb_U_GAIN, 0x38}, - {SENSOR, PO1030_Cr_V_GAIN, 0x38}, - {SENSOR, PO1030_CONTROL1, PO1030_SHUTTER_MODE | - PO1030_AUTO_SUBSAMPLING | - PO1030_FRAME_EQUAL}, - {SENSOR, PO1030_GC0, 0x10}, - {SENSOR, PO1030_GC1, 0x20}, - {SENSOR, PO1030_GC2, 0x40}, - {SENSOR, PO1030_GC3, 0x60}, - {SENSOR, PO1030_GC4, 0x80}, - {SENSOR, PO1030_GC5, 0xa0}, - {SENSOR, PO1030_GC6, 0xc0}, - {SENSOR, PO1030_GC7, 0xff}, - - /* Set the width to 751 */ - {SENSOR, PO1030_FRAMEWIDTH_H, 0x02}, - {SENSOR, PO1030_FRAMEWIDTH_L, 0xef}, - - /* Set the height to 540 */ - {SENSOR, PO1030_FRAMEHEIGHT_H, 0x02}, - {SENSOR, PO1030_FRAMEHEIGHT_L, 0x1c}, - - /* Set the x window to 1 */ - {SENSOR, PO1030_WINDOWX_H, 0x00}, - {SENSOR, PO1030_WINDOWX_L, 0x01}, - - /* Set the y window to 1 */ - {SENSOR, PO1030_WINDOWY_H, 0x00}, - {SENSOR, PO1030_WINDOWY_L, 0x01}, - - /* with a very low lighted environment increase the exposure but - * decrease the FPS (Frame Per Second) */ - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0}, - - {BRIDGE, M5602_XB_GPIO_DIR, 0x05}, - {BRIDGE, M5602_XB_GPIO_DAT, 0x00}, - {BRIDGE, M5602_XB_GPIO_EN_H, 0x06}, - {BRIDGE, M5602_XB_GPIO_EN_L, 0x00}, -}; #endif diff --git a/drivers/media/usb/gspca/m5602/m5602_s5k4aa.c b/drivers/media/usb/gspca/m5602/m5602_s5k4aa.c index 7d12599458e2..8447b9c5f8e0 100644 --- a/drivers/media/usb/gspca/m5602/m5602_s5k4aa.c +++ b/drivers/media/usb/gspca/m5602/m5602_s5k4aa.c @@ -20,6 +20,205 @@ #include "m5602_s5k4aa.h" +static const unsigned char preinit_s5k4aa[][4] = { + {BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02, 0x00}, + {BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00}, + {BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00}, + {BRIDGE, M5602_XB_SENSOR_TYPE, 0x0d, 0x00}, + {BRIDGE, M5602_XB_SENSOR_CTRL, 0x00, 0x00}, + + {BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00}, + {BRIDGE, M5602_XB_GPIO_DAT, 0x08, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0xb0, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0x80, 0x00}, + {BRIDGE, M5602_XB_GPIO_EN_H, 0x3f, 0x00}, + {BRIDGE, M5602_XB_GPIO_DIR_H, 0x3f, 0x00}, + {BRIDGE, M5602_XB_GPIO_DAT_H, 0x00, 0x00}, + {BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00}, + {BRIDGE, M5602_XB_GPIO_DAT, 0x00, 0x00}, + {BRIDGE, M5602_XB_GPIO_EN_L, 0xff, 0x00}, + {BRIDGE, M5602_XB_GPIO_DIR_L, 0xff, 0x00}, + {BRIDGE, M5602_XB_GPIO_DAT_L, 0x00, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00}, + {BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00}, + {BRIDGE, M5602_XB_SENSOR_TYPE, 0x08, 0x00}, + + {BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02, 0x00}, + {BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0, 0x00}, + {BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00}, + {BRIDGE, M5602_XB_GPIO_DAT, 0x14, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xf0, 0x00}, + {BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00}, + {BRIDGE, M5602_XB_GPIO_DAT, 0x1c, 0x00}, + {BRIDGE, M5602_XB_GPIO_EN_H, 0x06, 0x00}, + {BRIDGE, M5602_XB_GPIO_DIR_H, 0x06, 0x00}, + {BRIDGE, M5602_XB_GPIO_DAT_H, 0x00, 0x00}, + {BRIDGE, M5602_XB_GPIO_EN_L, 0x00, 0x00}, + {BRIDGE, M5602_XB_I2C_CLK_DIV, 0x20, 0x00}, + + {SENSOR, S5K4AA_PAGE_MAP, 0x00, 0x00} +}; + +static const unsigned char init_s5k4aa[][4] = { + {BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02, 0x00}, + {BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00}, + {BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00}, + {BRIDGE, M5602_XB_SENSOR_TYPE, 0x0d, 0x00}, + {BRIDGE, M5602_XB_SENSOR_CTRL, 0x00, 0x00}, + + {BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00}, + {BRIDGE, M5602_XB_GPIO_DAT, 0x08, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0xb0, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0x80, 0x00}, + {BRIDGE, M5602_XB_GPIO_EN_H, 0x3f, 0x00}, + {BRIDGE, M5602_XB_GPIO_DIR_H, 0x3f, 0x00}, + {BRIDGE, M5602_XB_GPIO_DAT_H, 0x00, 0x00}, + {BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00}, + {BRIDGE, M5602_XB_GPIO_DAT, 0x00, 0x00}, + {BRIDGE, M5602_XB_GPIO_EN_L, 0xff, 0x00}, + {BRIDGE, M5602_XB_GPIO_DIR_L, 0xff, 0x00}, + {BRIDGE, M5602_XB_GPIO_DAT_L, 0x00, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00}, + {BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00}, + {BRIDGE, M5602_XB_SENSOR_TYPE, 0x08, 0x00}, + + {BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02, 0x00}, + {BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0, 0x00}, + {BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00}, + {BRIDGE, M5602_XB_GPIO_DAT, 0x14, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xf0, 0x00}, + {BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00}, + {BRIDGE, M5602_XB_GPIO_DAT, 0x1c, 0x00}, + {BRIDGE, M5602_XB_GPIO_EN_H, 0x06, 0x00}, + {BRIDGE, M5602_XB_GPIO_DIR_H, 0x06, 0x00}, + {BRIDGE, M5602_XB_GPIO_DAT_H, 0x00, 0x00}, + {BRIDGE, M5602_XB_GPIO_EN_L, 0x00, 0x00}, + {BRIDGE, M5602_XB_I2C_CLK_DIV, 0x20, 0x00}, + + {SENSOR, S5K4AA_PAGE_MAP, 0x07, 0x00}, + {SENSOR, 0x36, 0x01, 0x00}, + {SENSOR, S5K4AA_PAGE_MAP, 0x00, 0x00}, + {SENSOR, 0x7b, 0xff, 0x00}, + {SENSOR, S5K4AA_PAGE_MAP, 0x02, 0x00}, + {SENSOR, 0x0c, 0x05, 0x00}, + {SENSOR, 0x02, 0x0e, 0x00}, + {SENSOR, S5K4AA_READ_MODE, 0xa0, 0x00}, + {SENSOR, 0x37, 0x00, 0x00}, +}; + +static const unsigned char VGA_s5k4aa[][4] = { + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x06, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00}, + {BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00}, + {BRIDGE, M5602_XB_SENSOR_TYPE, 0x08, 0x00}, + {BRIDGE, M5602_XB_LINE_OF_FRAME_H, 0x81, 0x00}, + {BRIDGE, M5602_XB_PIX_OF_LINE_H, 0x82, 0x00}, + {BRIDGE, M5602_XB_SIG_INI, 0x01, 0x00}, + {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, + {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, + {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, + {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, + /* VSYNC_PARA, VSYNC_PARA : img height 480 = 0x01e0 */ + {BRIDGE, M5602_XB_VSYNC_PARA, 0x01, 0x00}, + {BRIDGE, M5602_XB_VSYNC_PARA, 0xe0, 0x00}, + {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, + {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, + {BRIDGE, M5602_XB_SIG_INI, 0x00, 0x00}, + {BRIDGE, M5602_XB_SIG_INI, 0x02, 0x00}, + {BRIDGE, M5602_XB_HSYNC_PARA, 0x00, 0x00}, + {BRIDGE, M5602_XB_HSYNC_PARA, 0x00, 0x00}, + /* HSYNC_PARA, HSYNC_PARA : img width 640 = 0x0280 */ + {BRIDGE, M5602_XB_HSYNC_PARA, 0x02, 0x00}, + {BRIDGE, M5602_XB_HSYNC_PARA, 0x80, 0x00}, + {BRIDGE, M5602_XB_SIG_INI, 0x00, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xa0, 0x00}, /* 48 MHz */ + + {SENSOR, S5K4AA_PAGE_MAP, 0x02, 0x00}, + {SENSOR, S5K4AA_READ_MODE, S5K4AA_RM_H_FLIP | S5K4AA_RM_ROW_SKIP_2X + | S5K4AA_RM_COL_SKIP_2X, 0x00}, + /* 0x37 : Fix image stability when light is too bright and improves + * image quality in 640x480, but worsens it in 1280x1024 */ + {SENSOR, 0x37, 0x01, 0x00}, + /* ROWSTART_HI, ROWSTART_LO : 10 + (1024-960)/2 = 42 = 0x002a */ + {SENSOR, S5K4AA_ROWSTART_HI, 0x00, 0x00}, + {SENSOR, S5K4AA_ROWSTART_LO, 0x29, 0x00}, + {SENSOR, S5K4AA_COLSTART_HI, 0x00, 0x00}, + {SENSOR, S5K4AA_COLSTART_LO, 0x0c, 0x00}, + /* window_height_hi, window_height_lo : 960 = 0x03c0 */ + {SENSOR, S5K4AA_WINDOW_HEIGHT_HI, 0x03, 0x00}, + {SENSOR, S5K4AA_WINDOW_HEIGHT_LO, 0xc0, 0x00}, + /* window_width_hi, window_width_lo : 1280 = 0x0500 */ + {SENSOR, S5K4AA_WINDOW_WIDTH_HI, 0x05, 0x00}, + {SENSOR, S5K4AA_WINDOW_WIDTH_LO, 0x00, 0x00}, + {SENSOR, S5K4AA_H_BLANK_HI__, 0x00, 0x00}, + {SENSOR, S5K4AA_H_BLANK_LO__, 0xa8, 0x00}, /* helps to sync... */ + {SENSOR, S5K4AA_EXPOSURE_HI, 0x01, 0x00}, + {SENSOR, S5K4AA_EXPOSURE_LO, 0x00, 0x00}, + {SENSOR, 0x11, 0x04, 0x00}, + {SENSOR, 0x12, 0xc3, 0x00}, + {SENSOR, S5K4AA_PAGE_MAP, 0x02, 0x00}, + {SENSOR, 0x02, 0x0e, 0x00}, +}; + +static const unsigned char SXGA_s5k4aa[][4] = { + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x06, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00}, + {BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00}, + {BRIDGE, M5602_XB_SENSOR_TYPE, 0x08, 0x00}, + {BRIDGE, M5602_XB_LINE_OF_FRAME_H, 0x81, 0x00}, + {BRIDGE, M5602_XB_PIX_OF_LINE_H, 0x82, 0x00}, + {BRIDGE, M5602_XB_SIG_INI, 0x01, 0x00}, + {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, + {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, + {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, + {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, + /* VSYNC_PARA, VSYNC_PARA : img height 1024 = 0x0400 */ + {BRIDGE, M5602_XB_VSYNC_PARA, 0x04, 0x00}, + {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, + {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, + {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, + {BRIDGE, M5602_XB_SIG_INI, 0x00, 0x00}, + {BRIDGE, M5602_XB_SIG_INI, 0x02, 0x00}, + {BRIDGE, M5602_XB_HSYNC_PARA, 0x00, 0x00}, + {BRIDGE, M5602_XB_HSYNC_PARA, 0x00, 0x00}, + /* HSYNC_PARA, HSYNC_PARA : img width 1280 = 0x0500 */ + {BRIDGE, M5602_XB_HSYNC_PARA, 0x05, 0x00}, + {BRIDGE, M5602_XB_HSYNC_PARA, 0x00, 0x00}, + {BRIDGE, M5602_XB_SIG_INI, 0x00, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xa0, 0x00}, /* 48 MHz */ + + {SENSOR, S5K4AA_PAGE_MAP, 0x02, 0x00}, + {SENSOR, S5K4AA_READ_MODE, S5K4AA_RM_H_FLIP, 0x00}, + {SENSOR, 0x37, 0x01, 0x00}, + {SENSOR, S5K4AA_ROWSTART_HI, 0x00, 0x00}, + {SENSOR, S5K4AA_ROWSTART_LO, 0x09, 0x00}, + {SENSOR, S5K4AA_COLSTART_HI, 0x00, 0x00}, + {SENSOR, S5K4AA_COLSTART_LO, 0x0a, 0x00}, + {SENSOR, S5K4AA_WINDOW_HEIGHT_HI, 0x04, 0x00}, + {SENSOR, S5K4AA_WINDOW_HEIGHT_LO, 0x00, 0x00}, + {SENSOR, S5K4AA_WINDOW_WIDTH_HI, 0x05, 0x00}, + {SENSOR, S5K4AA_WINDOW_WIDTH_LO, 0x00, 0x00}, + {SENSOR, S5K4AA_H_BLANK_HI__, 0x01, 0x00}, + {SENSOR, S5K4AA_H_BLANK_LO__, 0xa8, 0x00}, + {SENSOR, S5K4AA_EXPOSURE_HI, 0x01, 0x00}, + {SENSOR, S5K4AA_EXPOSURE_LO, 0x00, 0x00}, + {SENSOR, 0x11, 0x04, 0x00}, + {SENSOR, 0x12, 0xc3, 0x00}, + {SENSOR, S5K4AA_PAGE_MAP, 0x02, 0x00}, + {SENSOR, 0x02, 0x0e, 0x00}, +}; + + static int s5k4aa_s_ctrl(struct v4l2_ctrl *ctrl); static void s5k4aa_dump_registers(struct sd *sd); diff --git a/drivers/media/usb/gspca/m5602/m5602_s5k4aa.h b/drivers/media/usb/gspca/m5602/m5602_s5k4aa.h index 9953e9766954..8407682d6823 100644 --- a/drivers/media/usb/gspca/m5602/m5602_s5k4aa.h +++ b/drivers/media/usb/gspca/m5602/m5602_s5k4aa.h @@ -85,201 +85,4 @@ static const struct m5602_sensor s5k4aa = { .disconnect = s5k4aa_disconnect, }; -static const unsigned char preinit_s5k4aa[][4] = { - {BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02, 0x00}, - {BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00}, - {BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00}, - {BRIDGE, M5602_XB_SENSOR_TYPE, 0x0d, 0x00}, - {BRIDGE, M5602_XB_SENSOR_CTRL, 0x00, 0x00}, - - {BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00}, - {BRIDGE, M5602_XB_GPIO_DAT, 0x08, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0xb0, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0x80, 0x00}, - {BRIDGE, M5602_XB_GPIO_EN_H, 0x3f, 0x00}, - {BRIDGE, M5602_XB_GPIO_DIR_H, 0x3f, 0x00}, - {BRIDGE, M5602_XB_GPIO_DAT_H, 0x00, 0x00}, - {BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00}, - {BRIDGE, M5602_XB_GPIO_DAT, 0x00, 0x00}, - {BRIDGE, M5602_XB_GPIO_EN_L, 0xff, 0x00}, - {BRIDGE, M5602_XB_GPIO_DIR_L, 0xff, 0x00}, - {BRIDGE, M5602_XB_GPIO_DAT_L, 0x00, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00}, - {BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00}, - {BRIDGE, M5602_XB_SENSOR_TYPE, 0x08, 0x00}, - - {BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02, 0x00}, - {BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0, 0x00}, - {BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00}, - {BRIDGE, M5602_XB_GPIO_DAT, 0x14, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xf0, 0x00}, - {BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00}, - {BRIDGE, M5602_XB_GPIO_DAT, 0x1c, 0x00}, - {BRIDGE, M5602_XB_GPIO_EN_H, 0x06, 0x00}, - {BRIDGE, M5602_XB_GPIO_DIR_H, 0x06, 0x00}, - {BRIDGE, M5602_XB_GPIO_DAT_H, 0x00, 0x00}, - {BRIDGE, M5602_XB_GPIO_EN_L, 0x00, 0x00}, - {BRIDGE, M5602_XB_I2C_CLK_DIV, 0x20, 0x00}, - - {SENSOR, S5K4AA_PAGE_MAP, 0x00, 0x00} -}; - -static const unsigned char init_s5k4aa[][4] = { - {BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02, 0x00}, - {BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00}, - {BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00}, - {BRIDGE, M5602_XB_SENSOR_TYPE, 0x0d, 0x00}, - {BRIDGE, M5602_XB_SENSOR_CTRL, 0x00, 0x00}, - - {BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00}, - {BRIDGE, M5602_XB_GPIO_DAT, 0x08, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0xb0, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0x80, 0x00}, - {BRIDGE, M5602_XB_GPIO_EN_H, 0x3f, 0x00}, - {BRIDGE, M5602_XB_GPIO_DIR_H, 0x3f, 0x00}, - {BRIDGE, M5602_XB_GPIO_DAT_H, 0x00, 0x00}, - {BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00}, - {BRIDGE, M5602_XB_GPIO_DAT, 0x00, 0x00}, - {BRIDGE, M5602_XB_GPIO_EN_L, 0xff, 0x00}, - {BRIDGE, M5602_XB_GPIO_DIR_L, 0xff, 0x00}, - {BRIDGE, M5602_XB_GPIO_DAT_L, 0x00, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00}, - {BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00}, - {BRIDGE, M5602_XB_SENSOR_TYPE, 0x08, 0x00}, - - {BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02, 0x00}, - {BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0, 0x00}, - {BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00}, - {BRIDGE, M5602_XB_GPIO_DAT, 0x14, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xf0, 0x00}, - {BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00}, - {BRIDGE, M5602_XB_GPIO_DAT, 0x1c, 0x00}, - {BRIDGE, M5602_XB_GPIO_EN_H, 0x06, 0x00}, - {BRIDGE, M5602_XB_GPIO_DIR_H, 0x06, 0x00}, - {BRIDGE, M5602_XB_GPIO_DAT_H, 0x00, 0x00}, - {BRIDGE, M5602_XB_GPIO_EN_L, 0x00, 0x00}, - {BRIDGE, M5602_XB_I2C_CLK_DIV, 0x20, 0x00}, - - {SENSOR, S5K4AA_PAGE_MAP, 0x07, 0x00}, - {SENSOR, 0x36, 0x01, 0x00}, - {SENSOR, S5K4AA_PAGE_MAP, 0x00, 0x00}, - {SENSOR, 0x7b, 0xff, 0x00}, - {SENSOR, S5K4AA_PAGE_MAP, 0x02, 0x00}, - {SENSOR, 0x0c, 0x05, 0x00}, - {SENSOR, 0x02, 0x0e, 0x00}, - {SENSOR, S5K4AA_READ_MODE, 0xa0, 0x00}, - {SENSOR, 0x37, 0x00, 0x00}, -}; - -static const unsigned char VGA_s5k4aa[][4] = { - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x06, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00}, - {BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00}, - {BRIDGE, M5602_XB_SENSOR_TYPE, 0x08, 0x00}, - {BRIDGE, M5602_XB_LINE_OF_FRAME_H, 0x81, 0x00}, - {BRIDGE, M5602_XB_PIX_OF_LINE_H, 0x82, 0x00}, - {BRIDGE, M5602_XB_SIG_INI, 0x01, 0x00}, - {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, - {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, - {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, - {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, - /* VSYNC_PARA, VSYNC_PARA : img height 480 = 0x01e0 */ - {BRIDGE, M5602_XB_VSYNC_PARA, 0x01, 0x00}, - {BRIDGE, M5602_XB_VSYNC_PARA, 0xe0, 0x00}, - {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, - {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, - {BRIDGE, M5602_XB_SIG_INI, 0x00, 0x00}, - {BRIDGE, M5602_XB_SIG_INI, 0x02, 0x00}, - {BRIDGE, M5602_XB_HSYNC_PARA, 0x00, 0x00}, - {BRIDGE, M5602_XB_HSYNC_PARA, 0x00, 0x00}, - /* HSYNC_PARA, HSYNC_PARA : img width 640 = 0x0280 */ - {BRIDGE, M5602_XB_HSYNC_PARA, 0x02, 0x00}, - {BRIDGE, M5602_XB_HSYNC_PARA, 0x80, 0x00}, - {BRIDGE, M5602_XB_SIG_INI, 0x00, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xa0, 0x00}, /* 48 MHz */ - - {SENSOR, S5K4AA_PAGE_MAP, 0x02, 0x00}, - {SENSOR, S5K4AA_READ_MODE, S5K4AA_RM_H_FLIP | S5K4AA_RM_ROW_SKIP_2X - | S5K4AA_RM_COL_SKIP_2X, 0x00}, - /* 0x37 : Fix image stability when light is too bright and improves - * image quality in 640x480, but worsens it in 1280x1024 */ - {SENSOR, 0x37, 0x01, 0x00}, - /* ROWSTART_HI, ROWSTART_LO : 10 + (1024-960)/2 = 42 = 0x002a */ - {SENSOR, S5K4AA_ROWSTART_HI, 0x00, 0x00}, - {SENSOR, S5K4AA_ROWSTART_LO, 0x29, 0x00}, - {SENSOR, S5K4AA_COLSTART_HI, 0x00, 0x00}, - {SENSOR, S5K4AA_COLSTART_LO, 0x0c, 0x00}, - /* window_height_hi, window_height_lo : 960 = 0x03c0 */ - {SENSOR, S5K4AA_WINDOW_HEIGHT_HI, 0x03, 0x00}, - {SENSOR, S5K4AA_WINDOW_HEIGHT_LO, 0xc0, 0x00}, - /* window_width_hi, window_width_lo : 1280 = 0x0500 */ - {SENSOR, S5K4AA_WINDOW_WIDTH_HI, 0x05, 0x00}, - {SENSOR, S5K4AA_WINDOW_WIDTH_LO, 0x00, 0x00}, - {SENSOR, S5K4AA_H_BLANK_HI__, 0x00, 0x00}, - {SENSOR, S5K4AA_H_BLANK_LO__, 0xa8, 0x00}, /* helps to sync... */ - {SENSOR, S5K4AA_EXPOSURE_HI, 0x01, 0x00}, - {SENSOR, S5K4AA_EXPOSURE_LO, 0x00, 0x00}, - {SENSOR, 0x11, 0x04, 0x00}, - {SENSOR, 0x12, 0xc3, 0x00}, - {SENSOR, S5K4AA_PAGE_MAP, 0x02, 0x00}, - {SENSOR, 0x02, 0x0e, 0x00}, -}; - -static const unsigned char SXGA_s5k4aa[][4] = { - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x06, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00}, - {BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00}, - {BRIDGE, M5602_XB_SENSOR_TYPE, 0x08, 0x00}, - {BRIDGE, M5602_XB_LINE_OF_FRAME_H, 0x81, 0x00}, - {BRIDGE, M5602_XB_PIX_OF_LINE_H, 0x82, 0x00}, - {BRIDGE, M5602_XB_SIG_INI, 0x01, 0x00}, - {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, - {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, - {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, - {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, - /* VSYNC_PARA, VSYNC_PARA : img height 1024 = 0x0400 */ - {BRIDGE, M5602_XB_VSYNC_PARA, 0x04, 0x00}, - {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, - {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, - {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, - {BRIDGE, M5602_XB_SIG_INI, 0x00, 0x00}, - {BRIDGE, M5602_XB_SIG_INI, 0x02, 0x00}, - {BRIDGE, M5602_XB_HSYNC_PARA, 0x00, 0x00}, - {BRIDGE, M5602_XB_HSYNC_PARA, 0x00, 0x00}, - /* HSYNC_PARA, HSYNC_PARA : img width 1280 = 0x0500 */ - {BRIDGE, M5602_XB_HSYNC_PARA, 0x05, 0x00}, - {BRIDGE, M5602_XB_HSYNC_PARA, 0x00, 0x00}, - {BRIDGE, M5602_XB_SIG_INI, 0x00, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xa0, 0x00}, /* 48 MHz */ - - {SENSOR, S5K4AA_PAGE_MAP, 0x02, 0x00}, - {SENSOR, S5K4AA_READ_MODE, S5K4AA_RM_H_FLIP, 0x00}, - {SENSOR, 0x37, 0x01, 0x00}, - {SENSOR, S5K4AA_ROWSTART_HI, 0x00, 0x00}, - {SENSOR, S5K4AA_ROWSTART_LO, 0x09, 0x00}, - {SENSOR, S5K4AA_COLSTART_HI, 0x00, 0x00}, - {SENSOR, S5K4AA_COLSTART_LO, 0x0a, 0x00}, - {SENSOR, S5K4AA_WINDOW_HEIGHT_HI, 0x04, 0x00}, - {SENSOR, S5K4AA_WINDOW_HEIGHT_LO, 0x00, 0x00}, - {SENSOR, S5K4AA_WINDOW_WIDTH_HI, 0x05, 0x00}, - {SENSOR, S5K4AA_WINDOW_WIDTH_LO, 0x00, 0x00}, - {SENSOR, S5K4AA_H_BLANK_HI__, 0x01, 0x00}, - {SENSOR, S5K4AA_H_BLANK_LO__, 0xa8, 0x00}, - {SENSOR, S5K4AA_EXPOSURE_HI, 0x01, 0x00}, - {SENSOR, S5K4AA_EXPOSURE_LO, 0x00, 0x00}, - {SENSOR, 0x11, 0x04, 0x00}, - {SENSOR, 0x12, 0xc3, 0x00}, - {SENSOR, S5K4AA_PAGE_MAP, 0x02, 0x00}, - {SENSOR, 0x02, 0x0e, 0x00}, -}; #endif diff --git a/drivers/media/usb/gspca/m5602/m5602_s5k83a.c b/drivers/media/usb/gspca/m5602/m5602_s5k83a.c index bf6b215438e3..be5e25d1a2e8 100644 --- a/drivers/media/usb/gspca/m5602/m5602_s5k83a.c +++ b/drivers/media/usb/gspca/m5602/m5602_s5k83a.c @@ -41,6 +41,130 @@ static struct v4l2_pix_format s5k83a_modes[] = { } }; +static const unsigned char preinit_s5k83a[][4] = { + {BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02, 0x00}, + {BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00}, + {BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00}, + {BRIDGE, M5602_XB_SENSOR_TYPE, 0x0d, 0x00}, + {BRIDGE, M5602_XB_SENSOR_CTRL, 0x00, 0x00}, + + {BRIDGE, M5602_XB_SIG_INI, 0x00, 0x00}, + {BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00}, + {BRIDGE, M5602_XB_GPIO_DAT, 0x08, 0x00}, + {BRIDGE, M5602_XB_GPIO_EN_H, 0x3f, 0x00}, + {BRIDGE, M5602_XB_GPIO_DIR_H, 0x3f, 0x00}, + {BRIDGE, M5602_XB_GPIO_DAT_H, 0x00, 0x00}, + {BRIDGE, M5602_XB_GPIO_EN_L, 0xff, 0x00}, + {BRIDGE, M5602_XB_GPIO_DIR_L, 0xff, 0x00}, + {BRIDGE, M5602_XB_GPIO_DAT_L, 0x00, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0xb0, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0x80, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00}, + {BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00}, + {BRIDGE, M5602_XB_SENSOR_TYPE, 0x09, 0x00}, + {BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02, 0x00}, + {BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xf0, 0x00}, + {BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00}, + {BRIDGE, M5602_XB_GPIO_DAT, 0x1c, 0x00}, + {BRIDGE, M5602_XB_GPIO_EN_H, 0x06, 0x00}, + {BRIDGE, M5602_XB_GPIO_DIR_H, 0x06, 0x00}, + {BRIDGE, M5602_XB_GPIO_DAT_H, 0x00, 0x00}, + {BRIDGE, M5602_XB_GPIO_EN_L, 0x00, 0x00}, + {BRIDGE, M5602_XB_I2C_CLK_DIV, 0x20, 0x00}, +}; + +/* This could probably be considerably shortened. + I don't have the hardware to experiment with it, patches welcome +*/ +static const unsigned char init_s5k83a[][4] = { + /* The following sequence is useless after a clean boot + but is necessary after resume from suspend */ + {BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00}, + {BRIDGE, M5602_XB_GPIO_DAT, 0x08, 0x00}, + {BRIDGE, M5602_XB_GPIO_EN_H, 0x3f, 0x00}, + {BRIDGE, M5602_XB_GPIO_DIR_H, 0x3f, 0x00}, + {BRIDGE, M5602_XB_GPIO_DAT_H, 0x00, 0x00}, + {BRIDGE, M5602_XB_GPIO_EN_L, 0xff, 0x00}, + {BRIDGE, M5602_XB_GPIO_DIR_L, 0xff, 0x00}, + {BRIDGE, M5602_XB_GPIO_DAT_L, 0x00, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0xb0, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0x80, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00}, + {BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00}, + {BRIDGE, M5602_XB_SENSOR_TYPE, 0x09, 0x00}, + {BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02, 0x00}, + {BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xf0, 0x00}, + {BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00}, + {BRIDGE, M5602_XB_GPIO_DAT, 0x08, 0x00}, + {BRIDGE, M5602_XB_GPIO_EN_H, 0x06, 0x00}, + {BRIDGE, M5602_XB_GPIO_DIR_H, 0x06, 0x00}, + {BRIDGE, M5602_XB_GPIO_DAT_H, 0x00, 0x00}, + {BRIDGE, M5602_XB_GPIO_EN_L, 0x00, 0x00}, + {BRIDGE, M5602_XB_I2C_CLK_DIV, 0x20, 0x00}, + + {SENSOR, S5K83A_PAGE_MAP, 0x04, 0x00}, + {SENSOR, 0xaf, 0x01, 0x00}, + {SENSOR, S5K83A_PAGE_MAP, 0x00, 0x00}, + {SENSOR, 0x7b, 0xff, 0x00}, + {SENSOR, S5K83A_PAGE_MAP, 0x05, 0x00}, + {SENSOR, 0x01, 0x50, 0x00}, + {SENSOR, 0x12, 0x20, 0x00}, + {SENSOR, 0x17, 0x40, 0x00}, + {SENSOR, 0x1c, 0x00, 0x00}, + {SENSOR, 0x02, 0x70, 0x00}, + {SENSOR, 0x03, 0x0b, 0x00}, + {SENSOR, 0x04, 0xf0, 0x00}, + {SENSOR, 0x05, 0x0b, 0x00}, + {SENSOR, 0x06, 0x71, 0x00}, + {SENSOR, 0x07, 0xe8, 0x00}, /* 488 */ + {SENSOR, 0x08, 0x02, 0x00}, + {SENSOR, 0x09, 0x88, 0x00}, /* 648 */ + {SENSOR, 0x14, 0x00, 0x00}, + {SENSOR, 0x15, 0x20, 0x00}, /* 32 */ + {SENSOR, 0x19, 0x00, 0x00}, + {SENSOR, 0x1a, 0x98, 0x00}, /* 152 */ + {SENSOR, 0x0f, 0x02, 0x00}, + {SENSOR, 0x10, 0xe5, 0x00}, /* 741 */ + /* normal colors + (this is value after boot, but after tries can be different) */ + {SENSOR, 0x00, 0x06, 0x00}, +}; + +static const unsigned char start_s5k83a[][4] = { + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x06, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00}, + {BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00}, + {BRIDGE, M5602_XB_SENSOR_TYPE, 0x09, 0x00}, + {BRIDGE, M5602_XB_LINE_OF_FRAME_H, 0x81, 0x00}, + {BRIDGE, M5602_XB_PIX_OF_LINE_H, 0x82, 0x00}, + {BRIDGE, M5602_XB_SIG_INI, 0x01, 0x00}, + {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, + {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, + {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, + {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, + {BRIDGE, M5602_XB_VSYNC_PARA, 0x01, 0x00}, + {BRIDGE, M5602_XB_VSYNC_PARA, 0xe4, 0x00}, /* 484 */ + {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, + {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, + {BRIDGE, M5602_XB_SIG_INI, 0x00, 0x00}, + {BRIDGE, M5602_XB_SIG_INI, 0x02, 0x00}, + {BRIDGE, M5602_XB_HSYNC_PARA, 0x00, 0x00}, + {BRIDGE, M5602_XB_HSYNC_PARA, 0x00, 0x00}, + {BRIDGE, M5602_XB_HSYNC_PARA, 0x02, 0x00}, + {BRIDGE, M5602_XB_HSYNC_PARA, 0x7f, 0x00}, /* 639 */ + {BRIDGE, M5602_XB_SIG_INI, 0x00, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00}, + {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00}, +}; + static void s5k83a_dump_registers(struct sd *sd); static int s5k83a_get_rotation(struct sd *sd, u8 *reg_data); static int s5k83a_set_led_indication(struct sd *sd, u8 val); diff --git a/drivers/media/usb/gspca/m5602/m5602_s5k83a.h b/drivers/media/usb/gspca/m5602/m5602_s5k83a.h index d61b918228df..3212bfe53d22 100644 --- a/drivers/media/usb/gspca/m5602/m5602_s5k83a.h +++ b/drivers/media/usb/gspca/m5602/m5602_s5k83a.h @@ -61,128 +61,4 @@ static const struct m5602_sensor s5k83a = { .i2c_slave_id = 0x5a, .i2c_regW = 2, }; - -static const unsigned char preinit_s5k83a[][4] = { - {BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02, 0x00}, - {BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00}, - {BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00}, - {BRIDGE, M5602_XB_SENSOR_TYPE, 0x0d, 0x00}, - {BRIDGE, M5602_XB_SENSOR_CTRL, 0x00, 0x00}, - - {BRIDGE, M5602_XB_SIG_INI, 0x00, 0x00}, - {BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00}, - {BRIDGE, M5602_XB_GPIO_DAT, 0x08, 0x00}, - {BRIDGE, M5602_XB_GPIO_EN_H, 0x3f, 0x00}, - {BRIDGE, M5602_XB_GPIO_DIR_H, 0x3f, 0x00}, - {BRIDGE, M5602_XB_GPIO_DAT_H, 0x00, 0x00}, - {BRIDGE, M5602_XB_GPIO_EN_L, 0xff, 0x00}, - {BRIDGE, M5602_XB_GPIO_DIR_L, 0xff, 0x00}, - {BRIDGE, M5602_XB_GPIO_DAT_L, 0x00, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0xb0, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0x80, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00}, - {BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00}, - {BRIDGE, M5602_XB_SENSOR_TYPE, 0x09, 0x00}, - {BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02, 0x00}, - {BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xf0, 0x00}, - {BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00}, - {BRIDGE, M5602_XB_GPIO_DAT, 0x1c, 0x00}, - {BRIDGE, M5602_XB_GPIO_EN_H, 0x06, 0x00}, - {BRIDGE, M5602_XB_GPIO_DIR_H, 0x06, 0x00}, - {BRIDGE, M5602_XB_GPIO_DAT_H, 0x00, 0x00}, - {BRIDGE, M5602_XB_GPIO_EN_L, 0x00, 0x00}, - {BRIDGE, M5602_XB_I2C_CLK_DIV, 0x20, 0x00}, -}; - -/* This could probably be considerably shortened. - I don't have the hardware to experiment with it, patches welcome -*/ -static const unsigned char init_s5k83a[][4] = { - /* The following sequence is useless after a clean boot - but is necessary after resume from suspend */ - {BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00}, - {BRIDGE, M5602_XB_GPIO_DAT, 0x08, 0x00}, - {BRIDGE, M5602_XB_GPIO_EN_H, 0x3f, 0x00}, - {BRIDGE, M5602_XB_GPIO_DIR_H, 0x3f, 0x00}, - {BRIDGE, M5602_XB_GPIO_DAT_H, 0x00, 0x00}, - {BRIDGE, M5602_XB_GPIO_EN_L, 0xff, 0x00}, - {BRIDGE, M5602_XB_GPIO_DIR_L, 0xff, 0x00}, - {BRIDGE, M5602_XB_GPIO_DAT_L, 0x00, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0xb0, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0x80, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00}, - {BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00}, - {BRIDGE, M5602_XB_SENSOR_TYPE, 0x09, 0x00}, - {BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02, 0x00}, - {BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xf0, 0x00}, - {BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00}, - {BRIDGE, M5602_XB_GPIO_DAT, 0x08, 0x00}, - {BRIDGE, M5602_XB_GPIO_EN_H, 0x06, 0x00}, - {BRIDGE, M5602_XB_GPIO_DIR_H, 0x06, 0x00}, - {BRIDGE, M5602_XB_GPIO_DAT_H, 0x00, 0x00}, - {BRIDGE, M5602_XB_GPIO_EN_L, 0x00, 0x00}, - {BRIDGE, M5602_XB_I2C_CLK_DIV, 0x20, 0x00}, - - {SENSOR, S5K83A_PAGE_MAP, 0x04, 0x00}, - {SENSOR, 0xaf, 0x01, 0x00}, - {SENSOR, S5K83A_PAGE_MAP, 0x00, 0x00}, - {SENSOR, 0x7b, 0xff, 0x00}, - {SENSOR, S5K83A_PAGE_MAP, 0x05, 0x00}, - {SENSOR, 0x01, 0x50, 0x00}, - {SENSOR, 0x12, 0x20, 0x00}, - {SENSOR, 0x17, 0x40, 0x00}, - {SENSOR, 0x1c, 0x00, 0x00}, - {SENSOR, 0x02, 0x70, 0x00}, - {SENSOR, 0x03, 0x0b, 0x00}, - {SENSOR, 0x04, 0xf0, 0x00}, - {SENSOR, 0x05, 0x0b, 0x00}, - {SENSOR, 0x06, 0x71, 0x00}, - {SENSOR, 0x07, 0xe8, 0x00}, /* 488 */ - {SENSOR, 0x08, 0x02, 0x00}, - {SENSOR, 0x09, 0x88, 0x00}, /* 648 */ - {SENSOR, 0x14, 0x00, 0x00}, - {SENSOR, 0x15, 0x20, 0x00}, /* 32 */ - {SENSOR, 0x19, 0x00, 0x00}, - {SENSOR, 0x1a, 0x98, 0x00}, /* 152 */ - {SENSOR, 0x0f, 0x02, 0x00}, - {SENSOR, 0x10, 0xe5, 0x00}, /* 741 */ - /* normal colors - (this is value after boot, but after tries can be different) */ - {SENSOR, 0x00, 0x06, 0x00}, -}; - -static const unsigned char start_s5k83a[][4] = { - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x06, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00}, - {BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00}, - {BRIDGE, M5602_XB_SENSOR_TYPE, 0x09, 0x00}, - {BRIDGE, M5602_XB_LINE_OF_FRAME_H, 0x81, 0x00}, - {BRIDGE, M5602_XB_PIX_OF_LINE_H, 0x82, 0x00}, - {BRIDGE, M5602_XB_SIG_INI, 0x01, 0x00}, - {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, - {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, - {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, - {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, - {BRIDGE, M5602_XB_VSYNC_PARA, 0x01, 0x00}, - {BRIDGE, M5602_XB_VSYNC_PARA, 0xe4, 0x00}, /* 484 */ - {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, - {BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00}, - {BRIDGE, M5602_XB_SIG_INI, 0x00, 0x00}, - {BRIDGE, M5602_XB_SIG_INI, 0x02, 0x00}, - {BRIDGE, M5602_XB_HSYNC_PARA, 0x00, 0x00}, - {BRIDGE, M5602_XB_HSYNC_PARA, 0x00, 0x00}, - {BRIDGE, M5602_XB_HSYNC_PARA, 0x02, 0x00}, - {BRIDGE, M5602_XB_HSYNC_PARA, 0x7f, 0x00}, /* 639 */ - {BRIDGE, M5602_XB_SIG_INI, 0x00, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00}, - {BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00}, -}; #endif diff --git a/drivers/media/usb/stk1160/stk1160-v4l.c b/drivers/media/usb/stk1160/stk1160-v4l.c index 77131fd614a5..7ddbc0274f12 100644 --- a/drivers/media/usb/stk1160/stk1160-v4l.c +++ b/drivers/media/usb/stk1160/stk1160-v4l.c @@ -680,6 +680,9 @@ static int queue_setup(struct vb2_queue *vq, *nbuffers = clamp_t(unsigned int, *nbuffers, STK1160_MIN_VIDEO_BUFFERS, STK1160_MAX_VIDEO_BUFFERS); + if (*nplanes) + return sizes[0] < size ? -EINVAL : 0; + /* This means a packed colorformat */ *nplanes = 1; diff --git a/drivers/media/usb/usbvision/usbvision-core.c b/drivers/media/usb/usbvision/usbvision-core.c index 1ea04e75fb36..52ac4391582c 100644 --- a/drivers/media/usb/usbvision/usbvision-core.c +++ b/drivers/media/usb/usbvision/usbvision-core.c @@ -88,11 +88,6 @@ MODULE_PARM_DESC(adjust_y_offset, "adjust Y offset display [core]"); #define DBG_SCRATCH (1 << 4) #define DBG_FUNC (1 << 5) -static const int max_imgwidth = MAX_FRAME_WIDTH; -static const int max_imgheight = MAX_FRAME_HEIGHT; -static const int min_imgwidth = MIN_FRAME_WIDTH; -static const int min_imgheight = MIN_FRAME_HEIGHT; - /* The value of 'scratch_buf_size' affects quality of the picture * in many ways. Shorter buffers may cause loss of data when client * is too slow. Larger buffers are memory-consuming and take longer diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c index 451e84e962e2..302e284a95eb 100644 --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -1674,7 +1674,7 @@ static void uvc_delete(struct uvc_device *dev) if (dev->vdev.dev) v4l2_device_unregister(&dev->vdev); #ifdef CONFIG_MEDIA_CONTROLLER - if (media_devnode_is_registered(&dev->mdev.devnode)) + if (media_devnode_is_registered(dev->mdev.devnode)) media_device_unregister(&dev->mdev); media_device_cleanup(&dev->mdev); #endif diff --git a/drivers/media/usb/uvc/uvc_v4l2.c b/drivers/media/usb/uvc/uvc_v4l2.c index d7723ce772b3..cded5ef52e24 100644 --- a/drivers/media/usb/uvc/uvc_v4l2.c +++ b/drivers/media/usb/uvc/uvc_v4l2.c @@ -142,6 +142,21 @@ static __u32 uvc_try_frame_interval(struct uvc_frame *frame, __u32 interval) return interval; } +static __u32 uvc_v4l2_get_bytesperline(const struct uvc_format *format, + const struct uvc_frame *frame) +{ + switch (format->fcc) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_YVU420: + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_M420: + return frame->wWidth; + + default: + return format->bpp * frame->wWidth / 8; + } +} + static int uvc_v4l2_try_format(struct uvc_streaming *stream, struct v4l2_format *fmt, struct uvc_streaming_control *probe, struct uvc_format **uvc_format, struct uvc_frame **uvc_frame) @@ -245,7 +260,7 @@ static int uvc_v4l2_try_format(struct uvc_streaming *stream, fmt->fmt.pix.width = frame->wWidth; fmt->fmt.pix.height = frame->wHeight; fmt->fmt.pix.field = V4L2_FIELD_NONE; - fmt->fmt.pix.bytesperline = format->bpp * frame->wWidth / 8; + fmt->fmt.pix.bytesperline = uvc_v4l2_get_bytesperline(format, frame); fmt->fmt.pix.sizeimage = probe->dwMaxVideoFrameSize; fmt->fmt.pix.colorspace = format->colorspace; fmt->fmt.pix.priv = 0; @@ -282,7 +297,7 @@ static int uvc_v4l2_get_format(struct uvc_streaming *stream, fmt->fmt.pix.width = frame->wWidth; fmt->fmt.pix.height = frame->wHeight; fmt->fmt.pix.field = V4L2_FIELD_NONE; - fmt->fmt.pix.bytesperline = format->bpp * frame->wWidth / 8; + fmt->fmt.pix.bytesperline = uvc_v4l2_get_bytesperline(format, frame); fmt->fmt.pix.sizeimage = stream->ctrl.dwMaxVideoFrameSize; fmt->fmt.pix.colorspace = format->colorspace; fmt->fmt.pix.priv = 0; diff --git a/drivers/media/usb/uvc/uvc_video.c b/drivers/media/usb/uvc/uvc_video.c index 075a0fe77485..b5589d5f5da4 100644 --- a/drivers/media/usb/uvc/uvc_video.c +++ b/drivers/media/usb/uvc/uvc_video.c @@ -1470,6 +1470,7 @@ static unsigned int uvc_endpoint_max_bpi(struct usb_device *dev, switch (dev->speed) { case USB_SPEED_SUPER: + case USB_SPEED_SUPER_PLUS: return le16_to_cpu(ep->ss_ep_comp.wBytesPerInterval); case USB_SPEED_HIGH: psize = usb_endpoint_maxp(&ep->desc); diff --git a/drivers/media/v4l2-core/v4l2-mc.c b/drivers/media/v4l2-core/v4l2-mc.c index ca94bded3386..8bef4331bd51 100644 --- a/drivers/media/v4l2-core/v4l2-mc.c +++ b/drivers/media/v4l2-core/v4l2-mc.c @@ -1,7 +1,7 @@ /* * Media Controller ancillary functions * - * Copyright (c) 2016 Mauro Carvalho Chehab <mchehab@osg.samsung.com> + * Copyright (c) 2016 Mauro Carvalho Chehab <mchehab@kernel.org> * Copyright (C) 2016 Shuah Khan <shuahkh@osg.samsung.com> * Copyright (C) 2006-2010 Nokia Corporation * Copyright (c) 2016 Intel Corporation. diff --git a/drivers/media/v4l2-core/videobuf2-core.c b/drivers/media/v4l2-core/videobuf2-core.c index 9fbcb67a9ee6..633fc1ab1d7a 100644 --- a/drivers/media/v4l2-core/videobuf2-core.c +++ b/drivers/media/v4l2-core/videobuf2-core.c @@ -1648,7 +1648,7 @@ static int __vb2_get_done_vb(struct vb2_queue *q, struct vb2_buffer **vb, void *pb, int nonblocking) { unsigned long flags; - int ret; + int ret = 0; /* * Wait for at least one buffer to become available on the done_list. @@ -1664,10 +1664,12 @@ static int __vb2_get_done_vb(struct vb2_queue *q, struct vb2_buffer **vb, spin_lock_irqsave(&q->done_lock, flags); *vb = list_first_entry(&q->done_list, struct vb2_buffer, done_entry); /* - * Only remove the buffer from done_list if v4l2_buffer can handle all - * the planes. + * Only remove the buffer from done_list if all planes can be + * handled. Some cases such as V4L2 file I/O and DVB have pb + * == NULL; skip the check then as there's nothing to verify. */ - ret = call_bufop(q, verify_planes_array, *vb, pb); + if (pb) + ret = call_bufop(q, verify_planes_array, *vb, pb); if (!ret) list_del(&(*vb)->done_entry); spin_unlock_irqrestore(&q->done_lock, flags); diff --git a/drivers/media/v4l2-core/videobuf2-dma-contig.c b/drivers/media/v4l2-core/videobuf2-dma-contig.c index 5361197f3e57..e3e47ace7daf 100644 --- a/drivers/media/v4l2-core/videobuf2-dma-contig.c +++ b/drivers/media/v4l2-core/videobuf2-dma-contig.c @@ -753,6 +753,59 @@ void vb2_dma_contig_cleanup_ctx(void *alloc_ctx) } EXPORT_SYMBOL_GPL(vb2_dma_contig_cleanup_ctx); +/** + * vb2_dma_contig_set_max_seg_size() - configure DMA max segment size + * @dev: device for configuring DMA parameters + * @size: size of DMA max segment size to set + * + * To allow mapping the scatter-list into a single chunk in the DMA + * address space, the device is required to have the DMA max segment + * size parameter set to a value larger than the buffer size. Otherwise, + * the DMA-mapping subsystem will split the mapping into max segment + * size chunks. This function sets the DMA max segment size + * parameter to let DMA-mapping map a buffer as a single chunk in DMA + * address space. + * This code assumes that the DMA-mapping subsystem will merge all + * scatterlist segments if this is really possible (for example when + * an IOMMU is available and enabled). + * Ideally, this parameter should be set by the generic bus code, but it + * is left with the default 64KiB value due to historical litmiations in + * other subsystems (like limited USB host drivers) and there no good + * place to set it to the proper value. + * This function should be called from the drivers, which are known to + * operate on platforms with IOMMU and provide access to shared buffers + * (either USERPTR or DMABUF). This should be done before initializing + * videobuf2 queue. + */ +int vb2_dma_contig_set_max_seg_size(struct device *dev, unsigned int size) +{ + if (!dev->dma_parms) { + dev->dma_parms = kzalloc(sizeof(dev->dma_parms), GFP_KERNEL); + if (!dev->dma_parms) + return -ENOMEM; + } + if (dma_get_max_seg_size(dev) < size) + return dma_set_max_seg_size(dev, size); + + return 0; +} +EXPORT_SYMBOL_GPL(vb2_dma_contig_set_max_seg_size); + +/* + * vb2_dma_contig_clear_max_seg_size() - release resources for DMA parameters + * @dev: device for configuring DMA parameters + * + * This function releases resources allocated to configure DMA parameters + * (see vb2_dma_contig_set_max_seg_size() function). It should be called from + * device drivers on driver remove. + */ +void vb2_dma_contig_clear_max_seg_size(struct device *dev) +{ + kfree(dev->dma_parms); + dev->dma_parms = NULL; +} +EXPORT_SYMBOL_GPL(vb2_dma_contig_clear_max_seg_size); + MODULE_DESCRIPTION("DMA-contig memory handling routines for videobuf2"); MODULE_AUTHOR("Pawel Osciak <pawel@osciak.com>"); MODULE_LICENSE("GPL"); diff --git a/drivers/media/v4l2-core/videobuf2-v4l2.c b/drivers/media/v4l2-core/videobuf2-v4l2.c index 0b1b8c7b6ce5..7f366f1b0377 100644 --- a/drivers/media/v4l2-core/videobuf2-v4l2.c +++ b/drivers/media/v4l2-core/videobuf2-v4l2.c @@ -74,6 +74,11 @@ static int __verify_planes_array(struct vb2_buffer *vb, const struct v4l2_buffer return 0; } +static int __verify_planes_array_core(struct vb2_buffer *vb, const void *pb) +{ + return __verify_planes_array(vb, pb); +} + /** * __verify_length() - Verify that the bytesused value for each plane fits in * the plane length and that the data offset doesn't exceed the bytesused value. @@ -437,6 +442,7 @@ static int __fill_vb2_buffer(struct vb2_buffer *vb, } static const struct vb2_buf_ops v4l2_buf_ops = { + .verify_planes_array = __verify_planes_array_core, .fill_user_buffer = __fill_v4l2_buffer, .fill_vb2_buffer = __fill_vb2_buffer, .copy_timestamp = __copy_timestamp, diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c index ed01c0172e4a..2b2291bfc38f 100644 --- a/drivers/of/of_reserved_mem.c +++ b/drivers/of/of_reserved_mem.c @@ -21,6 +21,7 @@ #include <linux/sizes.h> #include <linux/of_reserved_mem.h> #include <linux/sort.h> +#include <linux/slab.h> #define MAX_RESERVED_REGIONS 16 static struct reserved_mem reserved_mem[MAX_RESERVED_REGIONS]; @@ -289,53 +290,95 @@ static inline struct reserved_mem *__find_rmem(struct device_node *node) return NULL; } +struct rmem_assigned_device { + struct device *dev; + struct reserved_mem *rmem; + struct list_head list; +}; + +static LIST_HEAD(of_rmem_assigned_device_list); +static DEFINE_MUTEX(of_rmem_assigned_device_mutex); + /** - * of_reserved_mem_device_init() - assign reserved memory region to given device + * of_reserved_mem_device_init_by_idx() - assign reserved memory region to + * given device + * @dev: Pointer to the device to configure + * @np: Pointer to the device_node with 'reserved-memory' property + * @idx: Index of selected region * - * This function assign memory region pointed by "memory-region" device tree - * property to the given device. + * This function assigns respective DMA-mapping operations based on reserved + * memory region specified by 'memory-region' property in @np node to the @dev + * device. When driver needs to use more than one reserved memory region, it + * should allocate child devices and initialize regions by name for each of + * child device. + * + * Returns error code or zero on success. */ -int of_reserved_mem_device_init(struct device *dev) +int of_reserved_mem_device_init_by_idx(struct device *dev, + struct device_node *np, int idx) { + struct rmem_assigned_device *rd; + struct device_node *target; struct reserved_mem *rmem; - struct device_node *np; int ret; - np = of_parse_phandle(dev->of_node, "memory-region", 0); - if (!np) + if (!np || !dev) + return -EINVAL; + + target = of_parse_phandle(np, "memory-region", idx); + if (!target) return -ENODEV; - rmem = __find_rmem(np); - of_node_put(np); + rmem = __find_rmem(target); + of_node_put(target); if (!rmem || !rmem->ops || !rmem->ops->device_init) return -EINVAL; + rd = kmalloc(sizeof(struct rmem_assigned_device), GFP_KERNEL); + if (!rd) + return -ENOMEM; + ret = rmem->ops->device_init(rmem, dev); - if (ret == 0) + if (ret == 0) { + rd->dev = dev; + rd->rmem = rmem; + + mutex_lock(&of_rmem_assigned_device_mutex); + list_add(&rd->list, &of_rmem_assigned_device_list); + mutex_unlock(&of_rmem_assigned_device_mutex); + dev_info(dev, "assigned reserved memory node %s\n", rmem->name); + } else { + kfree(rd); + } return ret; } -EXPORT_SYMBOL_GPL(of_reserved_mem_device_init); +EXPORT_SYMBOL_GPL(of_reserved_mem_device_init_by_idx); /** * of_reserved_mem_device_release() - release reserved memory device structures + * @dev: Pointer to the device to deconfigure * * This function releases structures allocated for memory region handling for * the given device. */ void of_reserved_mem_device_release(struct device *dev) { - struct reserved_mem *rmem; - struct device_node *np; - - np = of_parse_phandle(dev->of_node, "memory-region", 0); - if (!np) - return; - - rmem = __find_rmem(np); - of_node_put(np); + struct rmem_assigned_device *rd; + struct reserved_mem *rmem = NULL; + + mutex_lock(&of_rmem_assigned_device_mutex); + list_for_each_entry(rd, &of_rmem_assigned_device_list, list) { + if (rd->dev == dev) { + rmem = rd->rmem; + list_del(&rd->list); + kfree(rd); + break; + } + } + mutex_unlock(&of_rmem_assigned_device_mutex); if (!rmem || !rmem->ops || !rmem->ops->device_release) return; diff --git a/drivers/staging/media/Kconfig b/drivers/staging/media/Kconfig index de7e9f52e7eb..567078986c94 100644 --- a/drivers/staging/media/Kconfig +++ b/drivers/staging/media/Kconfig @@ -21,24 +21,18 @@ if STAGING_MEDIA # Please keep them in alphabetic order source "drivers/staging/media/bcm2048/Kconfig" +source "drivers/staging/media/cec/Kconfig" + source "drivers/staging/media/cxd2099/Kconfig" source "drivers/staging/media/davinci_vpfe/Kconfig" -source "drivers/staging/media/mn88472/Kconfig" - -source "drivers/staging/media/mx2/Kconfig" - -source "drivers/staging/media/mx3/Kconfig" - -source "drivers/staging/media/omap1/Kconfig" - source "drivers/staging/media/omap4iss/Kconfig" -source "drivers/staging/media/timb/Kconfig" - source "drivers/staging/media/tw686x-kh/Kconfig" +source "drivers/staging/media/s5p-cec/Kconfig" + # Keep LIRC at the end, as it has sub-menus source "drivers/staging/media/lirc/Kconfig" diff --git a/drivers/staging/media/Makefile b/drivers/staging/media/Makefile index 60a35b3a47e7..989c844f4996 100644 --- a/drivers/staging/media/Makefile +++ b/drivers/staging/media/Makefile @@ -1,11 +1,8 @@ obj-$(CONFIG_I2C_BCM2048) += bcm2048/ +obj-$(CONFIG_MEDIA_CEC) += cec/ +obj-$(CONFIG_VIDEO_SAMSUNG_S5P_CEC) += s5p-cec/ obj-$(CONFIG_DVB_CXD2099) += cxd2099/ obj-$(CONFIG_LIRC_STAGING) += lirc/ obj-$(CONFIG_VIDEO_DM365_VPFE) += davinci_vpfe/ -obj-$(CONFIG_VIDEO_MX2) += mx2/ -obj-$(CONFIG_VIDEO_MX3) += mx3/ -obj-$(CONFIG_VIDEO_OMAP1) += omap1/ obj-$(CONFIG_VIDEO_OMAP4) += omap4iss/ -obj-$(CONFIG_DVB_MN88472) += mn88472/ -obj-$(CONFIG_VIDEO_TIMBERDALE) += timb/ obj-$(CONFIG_VIDEO_TW686X_KH) += tw686x-kh/ diff --git a/drivers/staging/media/cec/Kconfig b/drivers/staging/media/cec/Kconfig new file mode 100644 index 000000000000..8a7aceeac815 --- /dev/null +++ b/drivers/staging/media/cec/Kconfig @@ -0,0 +1,14 @@ +config MEDIA_CEC + tristate "CEC API (EXPERIMENTAL)" + select MEDIA_CEC_EDID + ---help--- + Enable the CEC API. + + To compile this driver as a module, choose M here: the + module will be called cec. + +config MEDIA_CEC_DEBUG + bool "CEC debugfs interface (EXPERIMENTAL)" + depends on MEDIA_CEC && DEBUG_FS + ---help--- + Turns on the DebugFS interface for CEC devices. diff --git a/drivers/staging/media/cec/Makefile b/drivers/staging/media/cec/Makefile new file mode 100644 index 000000000000..426ef73b959f --- /dev/null +++ b/drivers/staging/media/cec/Makefile @@ -0,0 +1,3 @@ +cec-objs := cec-core.o cec-adap.o cec-api.o + +obj-$(CONFIG_MEDIA_CEC) += cec.o diff --git a/drivers/staging/media/cec/TODO b/drivers/staging/media/cec/TODO new file mode 100644 index 000000000000..a8f4b7df38c8 --- /dev/null +++ b/drivers/staging/media/cec/TODO @@ -0,0 +1,27 @@ +The reason why cec.c is still in staging is that I would like +to have a bit more confidence in the uABI. The kABI is fine, +no problem there, but I would like to let the public API mature +a bit. + +Once I'm confident that I didn't miss anything then the cec.c source +can move to drivers/media and the linux/cec.h and linux/cec-funcs.h +headers can move to uapi/linux and added to uapi/linux/Kbuild to make +them public. + +Hopefully this will happen later in 2016. + +Other TODOs: + +- Add a flag to inhibit passing CEC RC messages to the rc subsystem. + Applications should be able to choose this when calling S_LOG_ADDRS. +- Convert cec.txt to sphinx. +- If the reply field of cec_msg is set then when the reply arrives it + is only sent to the filehandle that transmitted the original message + and not to any followers. Should this behavior change or perhaps + controlled through a cec_msg flag? +- Should CEC_LOG_ADDR_TYPE_SPECIFIC be replaced by TYPE_2ND_TV and TYPE_PROCESSOR? + And also TYPE_SWITCH and TYPE_CDC_ONLY in addition to the TYPE_UNREGISTERED? + This should give the framework more information about the device type + since SPECIFIC and UNREGISTERED give no useful information. + +Hans Verkuil <hans.verkuil@cisco.com> diff --git a/drivers/staging/media/cec/cec-adap.c b/drivers/staging/media/cec/cec-adap.c new file mode 100644 index 000000000000..5ffa839e5dec --- /dev/null +++ b/drivers/staging/media/cec/cec-adap.c @@ -0,0 +1,1627 @@ +/* + * cec-adap.c - HDMI Consumer Electronics Control framework - CEC adapter + * + * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * This program is free software; you may redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/kmod.h> +#include <linux/ktime.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/string.h> +#include <linux/types.h> + +#include "cec-priv.h" + +static int cec_report_features(struct cec_adapter *adap, unsigned int la_idx); +static int cec_report_phys_addr(struct cec_adapter *adap, unsigned int la_idx); + +/* + * 400 ms is the time it takes for one 16 byte message to be + * transferred and 5 is the maximum number of retries. Add + * another 100 ms as a margin. So if the transmit doesn't + * finish before that time something is really wrong and we + * have to time out. + * + * This is a sign that something it really wrong and a warning + * will be issued. + */ +#define CEC_XFER_TIMEOUT_MS (5 * 400 + 100) + +#define call_op(adap, op, arg...) \ + (adap->ops->op ? adap->ops->op(adap, ## arg) : 0) + +#define call_void_op(adap, op, arg...) \ + do { \ + if (adap->ops->op) \ + adap->ops->op(adap, ## arg); \ + } while (0) + +static int cec_log_addr2idx(const struct cec_adapter *adap, u8 log_addr) +{ + int i; + + for (i = 0; i < adap->log_addrs.num_log_addrs; i++) + if (adap->log_addrs.log_addr[i] == log_addr) + return i; + return -1; +} + +static unsigned int cec_log_addr2dev(const struct cec_adapter *adap, u8 log_addr) +{ + int i = cec_log_addr2idx(adap, log_addr); + + return adap->log_addrs.primary_device_type[i < 0 ? 0 : i]; +} + +/* + * Queue a new event for this filehandle. If ts == 0, then set it + * to the current time. + * + * The two events that are currently defined do not need to keep track + * of intermediate events, so no actual queue of events is needed, + * instead just store the latest state and the total number of lost + * messages. + * + * Should new events be added in the future that require intermediate + * results to be queued as well, then a proper queue data structure is + * required. But until then, just keep it simple. + */ +void cec_queue_event_fh(struct cec_fh *fh, + const struct cec_event *new_ev, u64 ts) +{ + struct cec_event *ev = &fh->events[new_ev->event - 1]; + + if (ts == 0) + ts = ktime_get_ns(); + + mutex_lock(&fh->lock); + if (new_ev->event == CEC_EVENT_LOST_MSGS && + fh->pending_events & (1 << new_ev->event)) { + /* + * If there is already a lost_msgs event, then just + * update the lost_msgs count. This effectively + * merges the old and new events into one. + */ + ev->lost_msgs.lost_msgs += new_ev->lost_msgs.lost_msgs; + goto unlock; + } + + /* + * Intermediate states are not interesting, so just + * overwrite any older event. + */ + *ev = *new_ev; + ev->ts = ts; + fh->pending_events |= 1 << new_ev->event; + +unlock: + mutex_unlock(&fh->lock); + wake_up_interruptible(&fh->wait); +} + +/* Queue a new event for all open filehandles. */ +static void cec_queue_event(struct cec_adapter *adap, + const struct cec_event *ev) +{ + u64 ts = ktime_get_ns(); + struct cec_fh *fh; + + mutex_lock(&adap->devnode.fhs_lock); + list_for_each_entry(fh, &adap->devnode.fhs, list) + cec_queue_event_fh(fh, ev, ts); + mutex_unlock(&adap->devnode.fhs_lock); +} + +/* + * Queue a new message for this filehandle. If there is no more room + * in the queue, then send the LOST_MSGS event instead. + */ +static void cec_queue_msg_fh(struct cec_fh *fh, const struct cec_msg *msg) +{ + static const struct cec_event ev_lost_msg = { + .event = CEC_EVENT_LOST_MSGS, + .lost_msgs.lost_msgs = 1, + }; + struct cec_msg_entry *entry; + + mutex_lock(&fh->lock); + entry = kmalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) + goto lost_msgs; + + entry->msg = *msg; + /* Add new msg at the end of the queue */ + list_add_tail(&entry->list, &fh->msgs); + + /* + * if the queue now has more than CEC_MAX_MSG_QUEUE_SZ + * messages, drop the oldest one and send a lost message event. + */ + if (fh->queued_msgs == CEC_MAX_MSG_QUEUE_SZ) { + list_del(&entry->list); + goto lost_msgs; + } + fh->queued_msgs++; + mutex_unlock(&fh->lock); + wake_up_interruptible(&fh->wait); + return; + +lost_msgs: + mutex_unlock(&fh->lock); + cec_queue_event_fh(fh, &ev_lost_msg, 0); +} + +/* + * Queue the message for those filehandles that are in monitor mode. + * If valid_la is true (this message is for us or was sent by us), + * then pass it on to any monitoring filehandle. If this message + * isn't for us or from us, then only give it to filehandles that + * are in MONITOR_ALL mode. + * + * This can only happen if the CEC_CAP_MONITOR_ALL capability is + * set and the CEC adapter was placed in 'monitor all' mode. + */ +static void cec_queue_msg_monitor(struct cec_adapter *adap, + const struct cec_msg *msg, + bool valid_la) +{ + struct cec_fh *fh; + u32 monitor_mode = valid_la ? CEC_MODE_MONITOR : + CEC_MODE_MONITOR_ALL; + + mutex_lock(&adap->devnode.fhs_lock); + list_for_each_entry(fh, &adap->devnode.fhs, list) { + if (fh->mode_follower >= monitor_mode) + cec_queue_msg_fh(fh, msg); + } + mutex_unlock(&adap->devnode.fhs_lock); +} + +/* + * Queue the message for follower filehandles. + */ +static void cec_queue_msg_followers(struct cec_adapter *adap, + const struct cec_msg *msg) +{ + struct cec_fh *fh; + + mutex_lock(&adap->devnode.fhs_lock); + list_for_each_entry(fh, &adap->devnode.fhs, list) { + if (fh->mode_follower == CEC_MODE_FOLLOWER) + cec_queue_msg_fh(fh, msg); + } + mutex_unlock(&adap->devnode.fhs_lock); +} + +/* Notify userspace of an adapter state change. */ +static void cec_post_state_event(struct cec_adapter *adap) +{ + struct cec_event ev = { + .event = CEC_EVENT_STATE_CHANGE, + }; + + ev.state_change.phys_addr = adap->phys_addr; + ev.state_change.log_addr_mask = adap->log_addrs.log_addr_mask; + cec_queue_event(adap, &ev); +} + +/* + * A CEC transmit (and a possible wait for reply) completed. + * If this was in blocking mode, then complete it, otherwise + * queue the message for userspace to dequeue later. + * + * This function is called with adap->lock held. + */ +static void cec_data_completed(struct cec_data *data) +{ + /* + * Delete this transmit from the filehandle's xfer_list since + * we're done with it. + * + * Note that if the filehandle is closed before this transmit + * finished, then the release() function will set data->fh to NULL. + * Without that we would be referring to a closed filehandle. + */ + if (data->fh) + list_del(&data->xfer_list); + + if (data->blocking) { + /* + * Someone is blocking so mark the message as completed + * and call complete. + */ + data->completed = true; + complete(&data->c); + } else { + /* + * No blocking, so just queue the message if needed and + * free the memory. + */ + if (data->fh) + cec_queue_msg_fh(data->fh, &data->msg); + kfree(data); + } +} + +/* + * A pending CEC transmit needs to be cancelled, either because the CEC + * adapter is disabled or the transmit takes an impossibly long time to + * finish. + * + * This function is called with adap->lock held. + */ +static void cec_data_cancel(struct cec_data *data) +{ + /* + * It's either the current transmit, or it is a pending + * transmit. Take the appropriate action to clear it. + */ + if (data->adap->transmitting == data) + data->adap->transmitting = NULL; + else + list_del_init(&data->list); + + /* Mark it as an error */ + data->msg.ts = ktime_get_ns(); + data->msg.tx_status = CEC_TX_STATUS_ERROR | + CEC_TX_STATUS_MAX_RETRIES; + data->attempts = 0; + data->msg.tx_error_cnt = 1; + data->msg.reply = 0; + /* Queue transmitted message for monitoring purposes */ + cec_queue_msg_monitor(data->adap, &data->msg, 1); + + cec_data_completed(data); +} + +/* + * Main CEC state machine + * + * Wait until the thread should be stopped, or we are not transmitting and + * a new transmit message is queued up, in which case we start transmitting + * that message. When the adapter finished transmitting the message it will + * call cec_transmit_done(). + * + * If the adapter is disabled, then remove all queued messages instead. + * + * If the current transmit times out, then cancel that transmit. + */ +int cec_thread_func(void *_adap) +{ + struct cec_adapter *adap = _adap; + + for (;;) { + unsigned int signal_free_time; + struct cec_data *data; + bool timeout = false; + u8 attempts; + + if (adap->transmitting) { + int err; + + /* + * We are transmitting a message, so add a timeout + * to prevent the state machine to get stuck waiting + * for this message to finalize and add a check to + * see if the adapter is disabled in which case the + * transmit should be canceled. + */ + err = wait_event_interruptible_timeout(adap->kthread_waitq, + kthread_should_stop() || + adap->phys_addr == CEC_PHYS_ADDR_INVALID || + (!adap->transmitting && + !list_empty(&adap->transmit_queue)), + msecs_to_jiffies(CEC_XFER_TIMEOUT_MS)); + timeout = err == 0; + } else { + /* Otherwise we just wait for something to happen. */ + wait_event_interruptible(adap->kthread_waitq, + kthread_should_stop() || + (!adap->transmitting && + !list_empty(&adap->transmit_queue))); + } + + mutex_lock(&adap->lock); + + if (adap->phys_addr == CEC_PHYS_ADDR_INVALID || + kthread_should_stop()) { + /* + * If the adapter is disabled, or we're asked to stop, + * then cancel any pending transmits. + */ + while (!list_empty(&adap->transmit_queue)) { + data = list_first_entry(&adap->transmit_queue, + struct cec_data, list); + cec_data_cancel(data); + } + if (adap->transmitting) + cec_data_cancel(adap->transmitting); + + /* + * Cancel the pending timeout work. We have to unlock + * the mutex when flushing the work since + * cec_wait_timeout() will take it. This is OK since + * no new entries can be added to wait_queue as long + * as adap->transmitting is NULL, which it is due to + * the cec_data_cancel() above. + */ + while (!list_empty(&adap->wait_queue)) { + data = list_first_entry(&adap->wait_queue, + struct cec_data, list); + + if (!cancel_delayed_work(&data->work)) { + mutex_unlock(&adap->lock); + flush_scheduled_work(); + mutex_lock(&adap->lock); + } + cec_data_cancel(data); + } + goto unlock; + } + + if (adap->transmitting && timeout) { + /* + * If we timeout, then log that. This really shouldn't + * 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", + adap->transmitting->msg.len, + adap->transmitting->msg.msg); + /* Just give up on this. */ + cec_data_cancel(adap->transmitting); + goto unlock; + } + + /* + * If we are still transmitting, or there is nothing new to + * transmit, then just continue waiting. + */ + if (adap->transmitting || list_empty(&adap->transmit_queue)) + goto unlock; + + /* Get a new message to transmit */ + data = list_first_entry(&adap->transmit_queue, + struct cec_data, list); + list_del_init(&data->list); + /* Make this the current transmitting message */ + adap->transmitting = data; + + /* + * Suggested number of attempts as per the CEC 2.0 spec: + * 4 attempts is the default, except for 'secondary poll + * messages', i.e. poll messages not sent during the adapter + * configuration phase when it allocates logical addresses. + */ + if (data->msg.len == 1 && adap->is_configured) + attempts = 2; + else + attempts = 4; + + /* Set the suggested signal free time */ + if (data->attempts) { + /* should be >= 3 data bit periods for a retry */ + signal_free_time = CEC_SIGNAL_FREE_TIME_RETRY; + } else if (data->new_initiator) { + /* should be >= 5 data bit periods for new initiator */ + signal_free_time = CEC_SIGNAL_FREE_TIME_NEW_INITIATOR; + } else { + /* + * should be >= 7 data bit periods for sending another + * frame immediately after another. + */ + signal_free_time = CEC_SIGNAL_FREE_TIME_NEXT_XFER; + } + if (data->attempts == 0) + data->attempts = attempts; + + /* Tell the adapter to transmit, cancel on error */ + if (adap->ops->adap_transmit(adap, data->attempts, + signal_free_time, &data->msg)) + cec_data_cancel(data); + +unlock: + mutex_unlock(&adap->lock); + + if (kthread_should_stop()) + break; + } + return 0; +} + +/* + * Called by the CEC adapter if a transmit finished. + */ +void cec_transmit_done(struct cec_adapter *adap, u8 status, u8 arb_lost_cnt, + u8 nack_cnt, u8 low_drive_cnt, u8 error_cnt) +{ + struct cec_data *data; + struct cec_msg *msg; + + dprintk(2, "cec_transmit_done %02x\n", status); + mutex_lock(&adap->lock); + data = adap->transmitting; + if (!data) { + /* + * This can happen if a transmit was issued and the cable is + * unplugged while the transmit is ongoing. Ignore this + * transmit in that case. + */ + dprintk(1, "cec_transmit_done without an ongoing transmit!\n"); + goto unlock; + } + + msg = &data->msg; + + /* Drivers must fill in the status! */ + WARN_ON(status == 0); + msg->ts = ktime_get_ns(); + msg->tx_status |= status; + msg->tx_arb_lost_cnt += arb_lost_cnt; + msg->tx_nack_cnt += nack_cnt; + msg->tx_low_drive_cnt += low_drive_cnt; + msg->tx_error_cnt += error_cnt; + + /* Mark that we're done with this transmit */ + adap->transmitting = NULL; + + /* + * If there are still retry attempts left and there was an error and + * the hardware didn't signal that it retried itself (by setting + * CEC_TX_STATUS_MAX_RETRIES), then we will retry ourselves. + */ + if (data->attempts > 1 && + !(status & (CEC_TX_STATUS_MAX_RETRIES | CEC_TX_STATUS_OK))) { + /* Retry this message */ + data->attempts--; + /* Add the message in front of the transmit queue */ + list_add(&data->list, &adap->transmit_queue); + goto wake_thread; + } + + data->attempts = 0; + + /* Always set CEC_TX_STATUS_MAX_RETRIES on error */ + if (!(status & CEC_TX_STATUS_OK)) + msg->tx_status |= CEC_TX_STATUS_MAX_RETRIES; + + /* Queue transmitted message for monitoring purposes */ + cec_queue_msg_monitor(adap, msg, 1); + + /* + * Clear reply and timeout on error or if the adapter is no longer + * configured. It makes no sense to wait for a reply in that case. + */ + if (!(status & CEC_TX_STATUS_OK) || !adap->is_configured) { + msg->reply = 0; + msg->timeout = 0; + } + + if (msg->timeout) { + /* + * Queue the message into the wait queue if we want to wait + * for a reply. + */ + list_add_tail(&data->list, &adap->wait_queue); + schedule_delayed_work(&data->work, + msecs_to_jiffies(msg->timeout)); + } else { + /* Otherwise we're done */ + cec_data_completed(data); + } + +wake_thread: + /* + * Wake up the main thread to see if another message is ready + * for transmitting or to retry the current message. + */ + wake_up_interruptible(&adap->kthread_waitq); +unlock: + mutex_unlock(&adap->lock); +} +EXPORT_SYMBOL_GPL(cec_transmit_done); + +/* + * Called when waiting for a reply times out. + */ +static void cec_wait_timeout(struct work_struct *work) +{ + struct cec_data *data = container_of(work, struct cec_data, work.work); + struct cec_adapter *adap = data->adap; + + mutex_lock(&adap->lock); + /* + * Sanity check in case the timeout and the arrival of the message + * happened at the same time. + */ + if (list_empty(&data->list)) + goto unlock; + + /* Mark the message as timed out */ + list_del_init(&data->list); + data->msg.ts = ktime_get_ns(); + data->msg.rx_status = CEC_RX_STATUS_TIMEOUT; + cec_data_completed(data); +unlock: + mutex_unlock(&adap->lock); +} + +/* + * Transmit a message. The fh argument may be NULL if the transmit is not + * associated with a specific filehandle. + * + * This function is called with adap->lock held. + */ +int cec_transmit_msg_fh(struct cec_adapter *adap, struct cec_msg *msg, + struct cec_fh *fh, bool block) +{ + struct cec_data *data; + u8 last_initiator = 0xff; + unsigned int timeout; + int res = 0; + + if (msg->reply && msg->timeout == 0) { + /* Make sure the timeout isn't 0. */ + msg->timeout = 1000; + } + + /* Sanity checks */ + if (msg->len == 0 || msg->len > CEC_MAX_MSG_SIZE) { + dprintk(1, "cec_transmit_msg: invalid length %d\n", msg->len); + return -EINVAL; + } + if (msg->timeout && msg->len == 1) { + dprintk(1, "cec_transmit_msg: can't reply for poll msg\n"); + return -EINVAL; + } + if (msg->len == 1) { + if (cec_msg_initiator(msg) != 0xf || + cec_msg_destination(msg) == 0xf) { + dprintk(1, "cec_transmit_msg: invalid poll message\n"); + return -EINVAL; + } + if (cec_has_log_addr(adap, cec_msg_destination(msg))) { + /* + * If the destination is a logical address our adapter + * has already claimed, then just NACK this. + * It depends on the hardware what it will do with a + * POLL to itself (some OK this), so it is just as + * easy to handle it here so the behavior will be + * consistent. + */ + msg->tx_status = CEC_TX_STATUS_NACK | + CEC_TX_STATUS_MAX_RETRIES; + msg->tx_nack_cnt = 1; + return 0; + } + } + if (msg->len > 1 && !cec_msg_is_broadcast(msg) && + cec_has_log_addr(adap, cec_msg_destination(msg))) { + dprintk(1, "cec_transmit_msg: destination is the adapter itself\n"); + return -EINVAL; + } + if (cec_msg_initiator(msg) != 0xf && + !cec_has_log_addr(adap, cec_msg_initiator(msg))) { + dprintk(1, "cec_transmit_msg: initiator has unknown logical address %d\n", + cec_msg_initiator(msg)); + return -EINVAL; + } + if (!adap->is_configured && !adap->is_configuring) + return -ENONET; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + if (msg->len > 1 && msg->msg[1] == CEC_MSG_CDC_MESSAGE) { + msg->msg[2] = adap->phys_addr >> 8; + msg->msg[3] = adap->phys_addr & 0xff; + } + + if (msg->timeout) + dprintk(2, "cec_transmit_msg: %*ph (wait for 0x%02x%s)\n", + msg->len, msg->msg, msg->reply, !block ? ", nb" : ""); + else + dprintk(2, "cec_transmit_msg: %*ph%s\n", + msg->len, msg->msg, !block ? " (nb)" : ""); + + msg->rx_status = 0; + msg->tx_status = 0; + msg->tx_arb_lost_cnt = 0; + msg->tx_nack_cnt = 0; + msg->tx_low_drive_cnt = 0; + msg->tx_error_cnt = 0; + data->msg = *msg; + data->fh = fh; + data->adap = adap; + data->blocking = block; + + /* + * Determine if this message follows a message from the same + * initiator. Needed to determine the free signal time later on. + */ + if (msg->len > 1) { + if (!(list_empty(&adap->transmit_queue))) { + const struct cec_data *last; + + last = list_last_entry(&adap->transmit_queue, + const struct cec_data, list); + last_initiator = cec_msg_initiator(&last->msg); + } else if (adap->transmitting) { + last_initiator = + cec_msg_initiator(&adap->transmitting->msg); + } + } + data->new_initiator = last_initiator != cec_msg_initiator(msg); + init_completion(&data->c); + INIT_DELAYED_WORK(&data->work, cec_wait_timeout); + + data->msg.sequence = adap->sequence++; + if (fh) + list_add_tail(&data->xfer_list, &fh->xfer_list); + list_add_tail(&data->list, &adap->transmit_queue); + if (!adap->transmitting) + wake_up_interruptible(&adap->kthread_waitq); + + /* All done if we don't need to block waiting for completion */ + if (!block) + return 0; + + /* + * If we don't get a completion before this time something is really + * wrong and we time out. + */ + timeout = CEC_XFER_TIMEOUT_MS; + /* Add the requested timeout if we have to wait for a reply as well */ + if (msg->timeout) + timeout += msg->timeout; + + /* + * Release the lock and wait, retake the lock afterwards. + */ + mutex_unlock(&adap->lock); + res = wait_for_completion_killable_timeout(&data->c, + msecs_to_jiffies(timeout)); + mutex_lock(&adap->lock); + + if (data->completed) { + /* The transmit completed (possibly with an error) */ + *msg = data->msg; + kfree(data); + return 0; + } + /* + * The wait for completion timed out or was interrupted, so mark this + * as non-blocking and disconnect from the filehandle since it is + * still 'in flight'. When it finally completes it will just drop the + * result silently. + */ + data->blocking = false; + if (data->fh) + list_del(&data->xfer_list); + data->fh = NULL; + + if (res == 0) { /* timed out */ + /* Check if the reply or the transmit failed */ + if (msg->timeout && (msg->tx_status & CEC_TX_STATUS_OK)) + msg->rx_status = CEC_RX_STATUS_TIMEOUT; + else + msg->tx_status = CEC_TX_STATUS_MAX_RETRIES; + } + return res > 0 ? 0 : res; +} + +/* Helper function to be used by drivers and this framework. */ +int cec_transmit_msg(struct cec_adapter *adap, struct cec_msg *msg, + bool block) +{ + int ret; + + mutex_lock(&adap->lock); + ret = cec_transmit_msg_fh(adap, msg, NULL, block); + mutex_unlock(&adap->lock); + return ret; +} +EXPORT_SYMBOL_GPL(cec_transmit_msg); + +/* + * I don't like forward references but without this the low-level + * cec_received_msg() function would come after a bunch of high-level + * CEC protocol handling functions. That was very confusing. + */ +static int cec_receive_notify(struct cec_adapter *adap, struct cec_msg *msg, + bool is_reply); + +/* Called by the CEC adapter if a message is received */ +void cec_received_msg(struct cec_adapter *adap, struct cec_msg *msg) +{ + struct cec_data *data; + u8 msg_init = cec_msg_initiator(msg); + u8 msg_dest = cec_msg_destination(msg); + bool is_reply = false; + bool valid_la = true; + + mutex_lock(&adap->lock); + msg->ts = ktime_get_ns(); + msg->rx_status = CEC_RX_STATUS_OK; + msg->tx_status = 0; + msg->sequence = msg->reply = msg->timeout = 0; + msg->flags = 0; + + dprintk(2, "cec_received_msg: %*ph\n", msg->len, msg->msg); + + /* Check if this message was for us (directed or broadcast). */ + if (!cec_msg_is_broadcast(msg)) + valid_la = cec_has_log_addr(adap, msg_dest); + + /* It's a valid message and not a poll or CDC message */ + if (valid_la && msg->len > 1 && msg->msg[1] != CEC_MSG_CDC_MESSAGE) { + u8 cmd = msg->msg[1]; + bool abort = cmd == CEC_MSG_FEATURE_ABORT; + + /* The aborted command is in msg[2] */ + if (abort) + cmd = msg->msg[2]; + + /* + * Walk over all transmitted messages that are waiting for a + * reply. + */ + list_for_each_entry(data, &adap->wait_queue, list) { + struct cec_msg *dst = &data->msg; + u8 dst_reply; + + /* Does the command match? */ + if ((abort && cmd != dst->msg[1]) || + (!abort && cmd != dst->reply)) + continue; + + /* Does the addressing match? */ + if (msg_init != cec_msg_destination(dst) && + !cec_msg_is_broadcast(dst)) + continue; + + /* We got a reply */ + msg->sequence = dst->sequence; + dst_reply = dst->reply; + *dst = *msg; + dst->reply = dst_reply; + if (abort) { + dst->reply = 0; + dst->rx_status |= CEC_RX_STATUS_FEATURE_ABORT; + } + /* Remove it from the wait_queue */ + list_del_init(&data->list); + + /* Cancel the pending timeout work */ + if (!cancel_delayed_work(&data->work)) { + mutex_unlock(&adap->lock); + flush_scheduled_work(); + mutex_lock(&adap->lock); + } + /* + * Mark this as a reply, provided someone is still + * waiting for the answer. + */ + if (data->fh) + is_reply = true; + cec_data_completed(data); + break; + } + } + mutex_unlock(&adap->lock); + + /* Pass the message on to any monitoring filehandles */ + cec_queue_msg_monitor(adap, msg, valid_la); + + /* We're done if it is not for us or a poll message */ + if (!valid_la || msg->len <= 1) + return; + + /* + * Process the message on the protocol level. If is_reply is true, + * then cec_receive_notify() won't pass on the reply to the listener(s) + * since that was already done by cec_data_completed() above. + */ + cec_receive_notify(adap, msg, is_reply); +} +EXPORT_SYMBOL_GPL(cec_received_msg); + +/* Logical Address Handling */ + +/* + * Attempt to claim a specific logical address. + * + * This function is called with adap->lock held. + */ +static int cec_config_log_addr(struct cec_adapter *adap, + unsigned int idx, + unsigned int log_addr) +{ + struct cec_log_addrs *las = &adap->log_addrs; + struct cec_msg msg = { }; + int err; + + if (cec_has_log_addr(adap, log_addr)) + return 0; + + /* Send poll message */ + msg.len = 1; + msg.msg[0] = 0xf0 | log_addr; + err = cec_transmit_msg_fh(adap, &msg, NULL, true); + + /* + * While trying to poll the physical address was reset + * and the adapter was unconfigured, so bail out. + */ + if (!adap->is_configuring) + return -EINTR; + + if (err) + return err; + + if (msg.tx_status & CEC_TX_STATUS_OK) + return 0; + + /* + * Message not acknowledged, so this logical + * address is free to use. + */ + err = adap->ops->adap_log_addr(adap, log_addr); + if (err) + return err; + + 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; +} + +/* + * Unconfigure the adapter: clear all logical addresses and send + * the state changed event. + * + * This function is called with adap->lock held. + */ +static void cec_adap_unconfigure(struct cec_adapter *adap) +{ + 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; + memset(adap->phys_addrs, 0xff, sizeof(adap->phys_addrs)); + wake_up_interruptible(&adap->kthread_waitq); + cec_post_state_event(adap); +} + +/* + * Attempt to claim the required logical addresses. + */ +static int cec_config_thread_func(void *arg) +{ + /* The various LAs for each type of device */ + static const u8 tv_log_addrs[] = { + CEC_LOG_ADDR_TV, CEC_LOG_ADDR_SPECIFIC, + CEC_LOG_ADDR_INVALID + }; + static const u8 record_log_addrs[] = { + CEC_LOG_ADDR_RECORD_1, CEC_LOG_ADDR_RECORD_2, + CEC_LOG_ADDR_RECORD_3, + CEC_LOG_ADDR_BACKUP_1, CEC_LOG_ADDR_BACKUP_2, + CEC_LOG_ADDR_INVALID + }; + static const u8 tuner_log_addrs[] = { + CEC_LOG_ADDR_TUNER_1, CEC_LOG_ADDR_TUNER_2, + CEC_LOG_ADDR_TUNER_3, CEC_LOG_ADDR_TUNER_4, + CEC_LOG_ADDR_BACKUP_1, CEC_LOG_ADDR_BACKUP_2, + CEC_LOG_ADDR_INVALID + }; + static const u8 playback_log_addrs[] = { + CEC_LOG_ADDR_PLAYBACK_1, CEC_LOG_ADDR_PLAYBACK_2, + CEC_LOG_ADDR_PLAYBACK_3, + CEC_LOG_ADDR_BACKUP_1, CEC_LOG_ADDR_BACKUP_2, + CEC_LOG_ADDR_INVALID + }; + static const u8 audiosystem_log_addrs[] = { + CEC_LOG_ADDR_AUDIOSYSTEM, + CEC_LOG_ADDR_INVALID + }; + static const u8 specific_use_log_addrs[] = { + CEC_LOG_ADDR_SPECIFIC, + CEC_LOG_ADDR_BACKUP_1, CEC_LOG_ADDR_BACKUP_2, + CEC_LOG_ADDR_INVALID + }; + static const u8 *type2addrs[6] = { + [CEC_LOG_ADDR_TYPE_TV] = tv_log_addrs, + [CEC_LOG_ADDR_TYPE_RECORD] = record_log_addrs, + [CEC_LOG_ADDR_TYPE_TUNER] = tuner_log_addrs, + [CEC_LOG_ADDR_TYPE_PLAYBACK] = playback_log_addrs, + [CEC_LOG_ADDR_TYPE_AUDIOSYSTEM] = audiosystem_log_addrs, + [CEC_LOG_ADDR_TYPE_SPECIFIC] = specific_use_log_addrs, + }; + static const u16 type2mask[] = { + [CEC_LOG_ADDR_TYPE_TV] = CEC_LOG_ADDR_MASK_TV, + [CEC_LOG_ADDR_TYPE_RECORD] = CEC_LOG_ADDR_MASK_RECORD, + [CEC_LOG_ADDR_TYPE_TUNER] = CEC_LOG_ADDR_MASK_TUNER, + [CEC_LOG_ADDR_TYPE_PLAYBACK] = CEC_LOG_ADDR_MASK_PLAYBACK, + [CEC_LOG_ADDR_TYPE_AUDIOSYSTEM] = CEC_LOG_ADDR_MASK_AUDIOSYSTEM, + [CEC_LOG_ADDR_TYPE_SPECIFIC] = CEC_LOG_ADDR_MASK_SPECIFIC, + }; + struct cec_adapter *adap = arg; + struct cec_log_addrs *las = &adap->log_addrs; + int err; + int i, j; + + mutex_lock(&adap->lock); + dprintk(1, "physical address: %x.%x.%x.%x, claim %d logical addresses\n", + cec_phys_addr_exp(adap->phys_addr), las->num_log_addrs); + las->log_addr_mask = 0; + + if (las->log_addr_type[0] == CEC_LOG_ADDR_TYPE_UNREGISTERED) + goto configured; + + for (i = 0; i < las->num_log_addrs; i++) { + unsigned int type = las->log_addr_type[i]; + const u8 *la_list; + u8 last_la; + + /* + * The TV functionality can only map to physical address 0. + * For any other address, try the Specific functionality + * instead as per the spec. + */ + if (adap->phys_addr && type == CEC_LOG_ADDR_TYPE_TV) + type = CEC_LOG_ADDR_TYPE_SPECIFIC; + + la_list = type2addrs[type]; + last_la = las->log_addr[i]; + las->log_addr[i] = CEC_LOG_ADDR_INVALID; + if (last_la == CEC_LOG_ADDR_INVALID || + last_la == CEC_LOG_ADDR_UNREGISTERED || + !(last_la & type2mask[type])) + last_la = la_list[0]; + + err = cec_config_log_addr(adap, i, last_la); + if (err > 0) /* Reused last LA */ + continue; + + if (err < 0) + goto unconfigure; + + for (j = 0; la_list[j] != CEC_LOG_ADDR_INVALID; j++) { + /* Tried this one already, skip it */ + if (la_list[j] == last_la) + continue; + /* The backup addresses are CEC 2.0 specific */ + if ((la_list[j] == CEC_LOG_ADDR_BACKUP_1 || + la_list[j] == CEC_LOG_ADDR_BACKUP_2) && + las->cec_version < CEC_OP_CEC_VERSION_2_0) + continue; + + err = cec_config_log_addr(adap, i, la_list[j]); + if (err == 0) /* LA is in use */ + continue; + if (err < 0) + goto unconfigure; + /* Done, claimed an LA */ + break; + } + + if (la_list[j] == CEC_LOG_ADDR_INVALID) + dprintk(1, "could not claim LA %d\n", i); + } + +configured: + if (adap->log_addrs.log_addr_mask == 0) { + /* Fall back to unregistered */ + las->log_addr[0] = CEC_LOG_ADDR_UNREGISTERED; + las->log_addr_mask = 1 << las->log_addr[0]; + } + adap->is_configured = true; + adap->is_configuring = false; + cec_post_state_event(adap); + mutex_unlock(&adap->lock); + + for (i = 0; i < las->num_log_addrs; i++) { + if (las->log_addr[i] == CEC_LOG_ADDR_INVALID) + continue; + + /* + * Report Features must come first according + * to CEC 2.0 + */ + if (las->log_addr[i] != CEC_LOG_ADDR_UNREGISTERED) + cec_report_features(adap, i); + cec_report_phys_addr(adap, i); + } + mutex_lock(&adap->lock); + adap->kthread_config = NULL; + mutex_unlock(&adap->lock); + complete(&adap->config_completion); + return 0; + +unconfigure: + for (i = 0; i < las->num_log_addrs; i++) + las->log_addr[i] = CEC_LOG_ADDR_INVALID; + cec_adap_unconfigure(adap); + adap->kthread_config = NULL; + mutex_unlock(&adap->lock); + complete(&adap->config_completion); + return 0; +} + +/* + * Called from either __cec_s_phys_addr or __cec_s_log_addrs to claim the + * logical addresses. + * + * This function is called with adap->lock held. + */ +static void cec_claim_log_addrs(struct cec_adapter *adap, bool block) +{ + if (WARN_ON(adap->is_configuring || adap->is_configured)) + return; + + init_completion(&adap->config_completion); + + /* Ready to kick off the thread */ + adap->is_configuring = true; + adap->kthread_config = kthread_run(cec_config_thread_func, adap, + "ceccfg-%s", adap->name); + if (IS_ERR(adap->kthread_config)) { + adap->kthread_config = NULL; + } else if (block) { + mutex_unlock(&adap->lock); + wait_for_completion(&adap->config_completion); + mutex_lock(&adap->lock); + } +} + +/* Set a new physical address and send an event notifying userspace of this. + * + * This function is called with adap->lock held. + */ +void __cec_s_phys_addr(struct cec_adapter *adap, u16 phys_addr, bool block) +{ + if (phys_addr == adap->phys_addr) + return; + + if (phys_addr == CEC_PHYS_ADDR_INVALID || + adap->phys_addr != CEC_PHYS_ADDR_INVALID) { + adap->phys_addr = CEC_PHYS_ADDR_INVALID; + cec_post_state_event(adap); + cec_adap_unconfigure(adap); + /* Disabling monitor all mode should always succeed */ + if (adap->monitor_all_cnt) + WARN_ON(call_op(adap, adap_monitor_all_enable, false)); + WARN_ON(adap->ops->adap_enable(adap, false)); + if (phys_addr == CEC_PHYS_ADDR_INVALID) + return; + } + + if (adap->ops->adap_enable(adap, true)) + return; + + if (adap->monitor_all_cnt && + call_op(adap, adap_monitor_all_enable, true)) { + WARN_ON(adap->ops->adap_enable(adap, false)); + return; + } + adap->phys_addr = phys_addr; + cec_post_state_event(adap); + if (adap->log_addrs.num_log_addrs) + cec_claim_log_addrs(adap, block); +} + +void cec_s_phys_addr(struct cec_adapter *adap, u16 phys_addr, bool block) +{ + if (IS_ERR_OR_NULL(adap)) + return; + + if (WARN_ON(adap->capabilities & CEC_CAP_PHYS_ADDR)) + return; + mutex_lock(&adap->lock); + __cec_s_phys_addr(adap, phys_addr, block); + mutex_unlock(&adap->lock); +} +EXPORT_SYMBOL_GPL(cec_s_phys_addr); + +/* + * Called from either the ioctl or a driver to set the logical addresses. + * + * This function is called with adap->lock held. + */ +int __cec_s_log_addrs(struct cec_adapter *adap, + struct cec_log_addrs *log_addrs, bool block) +{ + u16 type_mask = 0; + int i; + + if (!log_addrs || log_addrs->num_log_addrs == 0) { + adap->log_addrs.num_log_addrs = 0; + cec_adap_unconfigure(adap); + return 0; + } + + /* Ensure the osd name is 0-terminated */ + log_addrs->osd_name[sizeof(log_addrs->osd_name) - 1] = '\0'; + + /* Sanity checks */ + if (log_addrs->num_log_addrs > adap->available_log_addrs) { + dprintk(1, "num_log_addrs > %d\n", adap->available_log_addrs); + return -EINVAL; + } + + /* + * Vendor ID is a 24 bit number, so check if the value is + * within the correct range. + */ + if (log_addrs->vendor_id != CEC_VENDOR_ID_NONE && + (log_addrs->vendor_id & 0xff000000) != 0) + return -EINVAL; + + if (log_addrs->cec_version != CEC_OP_CEC_VERSION_1_4 && + log_addrs->cec_version != CEC_OP_CEC_VERSION_2_0) + return -EINVAL; + + if (log_addrs->num_log_addrs > 1) + for (i = 0; i < log_addrs->num_log_addrs; i++) + if (log_addrs->log_addr_type[i] == + CEC_LOG_ADDR_TYPE_UNREGISTERED) { + dprintk(1, "num_log_addrs > 1 can't be combined with unregistered LA\n"); + return -EINVAL; + } + + if (log_addrs->cec_version < CEC_OP_CEC_VERSION_2_0) { + memset(log_addrs->all_device_types, 0, + sizeof(log_addrs->all_device_types)); + memset(log_addrs->features, 0, sizeof(log_addrs->features)); + } + + for (i = 0; i < log_addrs->num_log_addrs; i++) { + u8 *features = log_addrs->features[i]; + bool op_is_dev_features = false; + + log_addrs->log_addr[i] = CEC_LOG_ADDR_INVALID; + if (type_mask & (1 << log_addrs->log_addr_type[i])) { + dprintk(1, "duplicate logical address type\n"); + return -EINVAL; + } + type_mask |= 1 << log_addrs->log_addr_type[i]; + if ((type_mask & (1 << CEC_LOG_ADDR_TYPE_RECORD)) && + (type_mask & (1 << CEC_LOG_ADDR_TYPE_PLAYBACK))) { + /* Record already contains the playback functionality */ + dprintk(1, "invalid record + playback combination\n"); + return -EINVAL; + } + if (log_addrs->primary_device_type[i] > + CEC_OP_PRIM_DEVTYPE_PROCESSOR) { + dprintk(1, "unknown primary device type\n"); + return -EINVAL; + } + if (log_addrs->primary_device_type[i] == 2) { + dprintk(1, "invalid primary device type\n"); + return -EINVAL; + } + if (log_addrs->log_addr_type[i] > CEC_LOG_ADDR_TYPE_UNREGISTERED) { + dprintk(1, "unknown logical address type\n"); + return -EINVAL; + } + if (log_addrs->cec_version < CEC_OP_CEC_VERSION_2_0) + continue; + + for (i = 0; i < ARRAY_SIZE(log_addrs->features[0]); i++) { + if ((features[i] & 0x80) == 0) { + if (op_is_dev_features) + break; + op_is_dev_features = true; + } + } + if (!op_is_dev_features || + i == ARRAY_SIZE(log_addrs->features[0])) { + dprintk(1, "malformed features\n"); + return -EINVAL; + } + } + + if (log_addrs->cec_version >= CEC_OP_CEC_VERSION_2_0) { + if (log_addrs->num_log_addrs > 2) { + dprintk(1, "CEC 2.0 allows no more than 2 logical addresses\n"); + return -EINVAL; + } + 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"); + 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"); + return -EINVAL; + } + } + } + + log_addrs->log_addr_mask = adap->log_addrs.log_addr_mask; + adap->log_addrs = *log_addrs; + if (adap->phys_addr != CEC_PHYS_ADDR_INVALID) + cec_claim_log_addrs(adap, block); + return 0; +} + +int cec_s_log_addrs(struct cec_adapter *adap, + struct cec_log_addrs *log_addrs, bool block) +{ + int err; + + if (WARN_ON(adap->capabilities & CEC_CAP_LOG_ADDRS)) + return -EINVAL; + mutex_lock(&adap->lock); + err = __cec_s_log_addrs(adap, log_addrs, block); + mutex_unlock(&adap->lock); + return err; +} +EXPORT_SYMBOL_GPL(cec_s_log_addrs); + +/* High-level core CEC message handling */ + +/* Transmit the Report Features message */ +static int cec_report_features(struct cec_adapter *adap, unsigned int la_idx) +{ + struct cec_msg msg = { }; + const struct cec_log_addrs *las = &adap->log_addrs; + const u8 *features = las->features[la_idx]; + bool op_is_dev_features = false; + unsigned int idx; + + /* This is 2.0 and up only */ + if (adap->log_addrs.cec_version < CEC_OP_CEC_VERSION_2_0) + return 0; + + /* Report Features */ + msg.msg[0] = (las->log_addr[la_idx] << 4) | 0x0f; + msg.len = 4; + msg.msg[1] = CEC_MSG_REPORT_FEATURES; + msg.msg[2] = adap->log_addrs.cec_version; + msg.msg[3] = las->all_device_types[la_idx]; + + /* Write RC Profiles first, then Device Features */ + for (idx = 0; idx < ARRAY_SIZE(las->features[0]); idx++) { + msg.msg[msg.len++] = features[idx]; + if ((features[idx] & CEC_OP_FEAT_EXT) == 0) { + if (op_is_dev_features) + break; + op_is_dev_features = true; + } + } + return cec_transmit_msg(adap, &msg, false); +} + +/* Transmit the Report Physical Address message */ +static int cec_report_phys_addr(struct cec_adapter *adap, unsigned int la_idx) +{ + const struct cec_log_addrs *las = &adap->log_addrs; + struct cec_msg msg = { }; + + /* Report Physical Address */ + msg.msg[0] = (las->log_addr[la_idx] << 4) | 0x0f; + cec_msg_report_physical_addr(&msg, adap->phys_addr, + las->primary_device_type[la_idx]); + dprintk(2, "config: la %d pa %x.%x.%x.%x\n", + las->log_addr[la_idx], + cec_phys_addr_exp(adap->phys_addr)); + return cec_transmit_msg(adap, &msg, false); +} + +/* Transmit the Feature Abort message */ +static int cec_feature_abort_reason(struct cec_adapter *adap, + struct cec_msg *msg, u8 reason) +{ + struct cec_msg tx_msg = { }; + + /* + * Don't reply with CEC_MSG_FEATURE_ABORT to a CEC_MSG_FEATURE_ABORT + * message! + */ + if (msg->msg[1] == CEC_MSG_FEATURE_ABORT) + return 0; + cec_msg_set_reply_to(&tx_msg, msg); + cec_msg_feature_abort(&tx_msg, msg->msg[1], reason); + return cec_transmit_msg(adap, &tx_msg, false); +} + +static int cec_feature_abort(struct cec_adapter *adap, struct cec_msg *msg) +{ + return cec_feature_abort_reason(adap, msg, + CEC_OP_ABORT_UNRECOGNIZED_OP); +} + +static int cec_feature_refused(struct cec_adapter *adap, struct cec_msg *msg) +{ + return cec_feature_abort_reason(adap, msg, + CEC_OP_ABORT_REFUSED); +} + +/* + * Called when a CEC message is received. This function will do any + * necessary core processing. The is_reply bool is true if this message + * is a reply to an earlier transmit. + * + * The message is either a broadcast message or a valid directed message. + */ +static int cec_receive_notify(struct cec_adapter *adap, struct cec_msg *msg, + bool is_reply) +{ + bool is_broadcast = cec_msg_is_broadcast(msg); + u8 dest_laddr = cec_msg_destination(msg); + u8 init_laddr = cec_msg_initiator(msg); + u8 devtype = cec_log_addr2dev(adap, dest_laddr); + int la_idx = cec_log_addr2idx(adap, dest_laddr); + bool is_directed = la_idx >= 0; + bool from_unregistered = init_laddr == 0xf; + struct cec_msg tx_cec_msg = { }; + + dprintk(1, "cec_receive_notify: %*ph\n", msg->len, msg->msg); + + if (adap->ops->received) { + /* Allow drivers to process the message first */ + if (adap->ops->received(adap, msg) != -ENOMSG) + return 0; + } + + /* + * REPORT_PHYSICAL_ADDR, CEC_MSG_USER_CONTROL_PRESSED and + * CEC_MSG_USER_CONTROL_RELEASED messages always have to be + * handled by the CEC core, even if the passthrough mode is on. + * The others are just ignored if passthrough mode is on. + */ + switch (msg->msg[1]) { + case CEC_MSG_GET_CEC_VERSION: + case CEC_MSG_GIVE_DEVICE_VENDOR_ID: + case CEC_MSG_ABORT: + case CEC_MSG_GIVE_DEVICE_POWER_STATUS: + case CEC_MSG_GIVE_PHYSICAL_ADDR: + case CEC_MSG_GIVE_OSD_NAME: + case CEC_MSG_GIVE_FEATURES: + /* + * Skip processing these messages if the passthrough mode + * is on. + */ + if (adap->passthrough) + goto skip_processing; + /* Ignore if addressing is wrong */ + if (is_broadcast || from_unregistered) + return 0; + break; + + case CEC_MSG_USER_CONTROL_PRESSED: + case CEC_MSG_USER_CONTROL_RELEASED: + /* Wrong addressing mode: don't process */ + if (is_broadcast || from_unregistered) + goto skip_processing; + break; + + case CEC_MSG_REPORT_PHYSICAL_ADDR: + /* + * This message is always processed, regardless of the + * passthrough setting. + * + * Exception: don't process if wrong addressing mode. + */ + if (!is_broadcast) + goto skip_processing; + break; + + default: + break; + } + + cec_msg_set_reply_to(&tx_cec_msg, msg); + + switch (msg->msg[1]) { + /* The following messages are processed but still passed through */ + case CEC_MSG_REPORT_PHYSICAL_ADDR: + adap->phys_addrs[init_laddr] = + (msg->msg[2] << 8) | msg->msg[3]; + dprintk(1, "Reported physical address %04x for logical address %d\n", + adap->phys_addrs[init_laddr], init_laddr); + break; + + case CEC_MSG_USER_CONTROL_PRESSED: + if (!(adap->capabilities & CEC_CAP_RC)) + break; + +#if IS_ENABLED(CONFIG_RC_CORE) + switch (msg->msg[2]) { + /* + * Play function, this message can have variable length + * depending on the specific play function that is used. + */ + case 0x60: + if (msg->len == 2) + rc_keydown(adap->rc, RC_TYPE_CEC, + msg->msg[2], 0); + else + rc_keydown(adap->rc, RC_TYPE_CEC, + msg->msg[2] << 8 | msg->msg[3], 0); + break; + /* + * Other function messages that are not handled. + * Currently the RC framework does not allow to supply an + * additional parameter to a keypress. These "keys" contain + * other information such as channel number, an input number + * etc. + * For the time being these messages are not processed by the + * framework and are simply forwarded to the user space. + */ + case 0x56: case 0x57: + case 0x67: case 0x68: case 0x69: case 0x6a: + break; + default: + rc_keydown(adap->rc, RC_TYPE_CEC, msg->msg[2], 0); + break; + } +#endif + break; + + case CEC_MSG_USER_CONTROL_RELEASED: + if (!(adap->capabilities & CEC_CAP_RC)) + break; +#if IS_ENABLED(CONFIG_RC_CORE) + rc_keyup(adap->rc); +#endif + break; + + /* + * The remaining messages are only processed if the passthrough mode + * is off. + */ + case CEC_MSG_GET_CEC_VERSION: + cec_msg_cec_version(&tx_cec_msg, adap->log_addrs.cec_version); + return cec_transmit_msg(adap, &tx_cec_msg, false); + + case CEC_MSG_GIVE_PHYSICAL_ADDR: + /* Do nothing for CEC switches using addr 15 */ + if (devtype == CEC_OP_PRIM_DEVTYPE_SWITCH && dest_laddr == 15) + return 0; + cec_msg_report_physical_addr(&tx_cec_msg, adap->phys_addr, devtype); + return cec_transmit_msg(adap, &tx_cec_msg, false); + + case CEC_MSG_GIVE_DEVICE_VENDOR_ID: + if (adap->log_addrs.vendor_id == CEC_VENDOR_ID_NONE) + return cec_feature_abort(adap, msg); + cec_msg_device_vendor_id(&tx_cec_msg, adap->log_addrs.vendor_id); + return cec_transmit_msg(adap, &tx_cec_msg, false); + + case CEC_MSG_ABORT: + /* Do nothing for CEC switches */ + if (devtype == CEC_OP_PRIM_DEVTYPE_SWITCH) + return 0; + return cec_feature_refused(adap, msg); + + case CEC_MSG_GIVE_OSD_NAME: { + if (adap->log_addrs.osd_name[0] == 0) + return cec_feature_abort(adap, msg); + cec_msg_set_osd_name(&tx_cec_msg, adap->log_addrs.osd_name); + return cec_transmit_msg(adap, &tx_cec_msg, false); + } + + case CEC_MSG_GIVE_FEATURES: + if (adap->log_addrs.cec_version >= CEC_OP_CEC_VERSION_2_0) + return cec_report_features(adap, la_idx); + return 0; + + default: + /* + * Unprocessed messages are aborted if userspace isn't doing + * any processing either. + */ + if (is_directed && !is_reply && !adap->follower_cnt && + !adap->cec_follower && msg->msg[1] != CEC_MSG_FEATURE_ABORT) + return cec_feature_abort(adap, msg); + break; + } + +skip_processing: + /* If this was a reply, then we're done */ + if (is_reply) + return 0; + + /* + * Send to the exclusive follower if there is one, otherwise send + * to all followers. + */ + if (adap->cec_follower) + cec_queue_msg_fh(adap->cec_follower, msg); + else + cec_queue_msg_followers(adap, msg); + return 0; +} + +/* + * Helper functions to keep track of the 'monitor all' use count. + * + * These functions are called with adap->lock held. + */ +int cec_monitor_all_cnt_inc(struct cec_adapter *adap) +{ + int ret = 0; + + if (adap->monitor_all_cnt == 0) + ret = call_op(adap, adap_monitor_all_enable, 1); + if (ret == 0) + adap->monitor_all_cnt++; + return ret; +} + +void cec_monitor_all_cnt_dec(struct cec_adapter *adap) +{ + adap->monitor_all_cnt--; + if (adap->monitor_all_cnt == 0) + WARN_ON(call_op(adap, adap_monitor_all_enable, 0)); +} + +#ifdef CONFIG_MEDIA_CEC_DEBUG +/* + * Log the current state of the CEC adapter. + * Very useful for debugging. + */ +int cec_adap_status(struct seq_file *file, void *priv) +{ + struct cec_adapter *adap = dev_get_drvdata(file->private); + struct cec_data *data; + + mutex_lock(&adap->lock); + seq_printf(file, "configured: %d\n", adap->is_configured); + seq_printf(file, "configuring: %d\n", adap->is_configuring); + seq_printf(file, "phys_addr: %x.%x.%x.%x\n", + cec_phys_addr_exp(adap->phys_addr)); + seq_printf(file, "number of LAs: %d\n", adap->log_addrs.num_log_addrs); + seq_printf(file, "LA mask: 0x%04x\n", adap->log_addrs.log_addr_mask); + if (adap->cec_follower) + seq_printf(file, "has CEC follower%s\n", + adap->passthrough ? " (in passthrough mode)" : ""); + if (adap->cec_initiator) + seq_puts(file, "has CEC initiator\n"); + if (adap->monitor_all_cnt) + seq_printf(file, "file handles in Monitor All mode: %u\n", + adap->monitor_all_cnt); + data = adap->transmitting; + if (data) + seq_printf(file, "transmitting message: %*ph (reply: %02x)\n", + data->msg.len, data->msg.msg, data->msg.reply); + list_for_each_entry(data, &adap->transmit_queue, list) { + seq_printf(file, "queued tx message: %*ph (reply: %02x)\n", + data->msg.len, data->msg.msg, data->msg.reply); + } + list_for_each_entry(data, &adap->wait_queue, list) { + seq_printf(file, "message waiting for reply: %*ph (reply: %02x)\n", + data->msg.len, data->msg.msg, data->msg.reply); + } + + call_void_op(adap, adap_status, file); + mutex_unlock(&adap->lock); + return 0; +} +#endif diff --git a/drivers/staging/media/cec/cec-api.c b/drivers/staging/media/cec/cec-api.c new file mode 100644 index 000000000000..d7cba7a7d6b0 --- /dev/null +++ b/drivers/staging/media/cec/cec-api.c @@ -0,0 +1,578 @@ +/* + * cec-api.c - HDMI Consumer Electronics Control framework - API + * + * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * This program is free software; you may redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/kmod.h> +#include <linux/ktime.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/uaccess.h> +#include <linux/version.h> + +#include "cec-priv.h" + +static inline struct cec_devnode *cec_devnode_data(struct file *filp) +{ + struct cec_fh *fh = filp->private_data; + + return &fh->adap->devnode; +} + +/* CEC file operations */ + +static unsigned int cec_poll(struct file *filp, + struct poll_table_struct *poll) +{ + struct cec_devnode *devnode = cec_devnode_data(filp); + struct cec_fh *fh = filp->private_data; + struct cec_adapter *adap = fh->adap; + unsigned int res = 0; + + if (!devnode->registered) + return POLLERR | POLLHUP; + mutex_lock(&adap->lock); + if (adap->is_configured) + res |= POLLOUT | POLLWRNORM; + if (fh->queued_msgs) + res |= POLLIN | POLLRDNORM; + if (fh->pending_events) + res |= POLLPRI; + poll_wait(filp, &fh->wait, poll); + mutex_unlock(&adap->lock); + return res; +} + +static bool cec_is_busy(const struct cec_adapter *adap, + const struct cec_fh *fh) +{ + bool valid_initiator = adap->cec_initiator && adap->cec_initiator == fh; + bool valid_follower = adap->cec_follower && adap->cec_follower == fh; + + /* + * Exclusive initiators and followers can always access the CEC adapter + */ + if (valid_initiator || valid_follower) + return false; + /* + * All others can only access the CEC adapter if there is no + * exclusive initiator and they are in INITIATOR mode. + */ + return adap->cec_initiator || + fh->mode_initiator == CEC_MODE_NO_INITIATOR; +} + +static long cec_adap_g_caps(struct cec_adapter *adap, + struct cec_caps __user *parg) +{ + struct cec_caps caps = {}; + + strlcpy(caps.driver, adap->devnode.parent->driver->name, + sizeof(caps.driver)); + strlcpy(caps.name, adap->name, sizeof(caps.name)); + caps.available_log_addrs = adap->available_log_addrs; + caps.capabilities = adap->capabilities; + caps.version = LINUX_VERSION_CODE; + if (copy_to_user(parg, &caps, sizeof(caps))) + return -EFAULT; + return 0; +} + +static long cec_adap_g_phys_addr(struct cec_adapter *adap, + __u16 __user *parg) +{ + u16 phys_addr; + + mutex_lock(&adap->lock); + phys_addr = adap->phys_addr; + mutex_unlock(&adap->lock); + if (copy_to_user(parg, &phys_addr, sizeof(phys_addr))) + return -EFAULT; + return 0; +} + +static long cec_adap_s_phys_addr(struct cec_adapter *adap, struct cec_fh *fh, + bool block, __u16 __user *parg) +{ + u16 phys_addr; + long err; + + if (!(adap->capabilities & CEC_CAP_PHYS_ADDR)) + return -ENOTTY; + if (copy_from_user(&phys_addr, parg, sizeof(phys_addr))) + return -EFAULT; + + err = cec_phys_addr_validate(phys_addr, NULL, NULL); + if (err) + return err; + mutex_lock(&adap->lock); + if (cec_is_busy(adap, fh)) + err = -EBUSY; + else + __cec_s_phys_addr(adap, phys_addr, block); + mutex_unlock(&adap->lock); + return err; +} + +static long cec_adap_g_log_addrs(struct cec_adapter *adap, + struct cec_log_addrs __user *parg) +{ + struct cec_log_addrs log_addrs; + + mutex_lock(&adap->lock); + log_addrs = adap->log_addrs; + if (!adap->is_configured) + memset(log_addrs.log_addr, CEC_LOG_ADDR_INVALID, + sizeof(log_addrs.log_addr)); + mutex_unlock(&adap->lock); + + if (copy_to_user(parg, &log_addrs, sizeof(log_addrs))) + return -EFAULT; + return 0; +} + +static long cec_adap_s_log_addrs(struct cec_adapter *adap, struct cec_fh *fh, + bool block, struct cec_log_addrs __user *parg) +{ + struct cec_log_addrs log_addrs; + long err = -EBUSY; + + if (!(adap->capabilities & CEC_CAP_LOG_ADDRS)) + return -ENOTTY; + if (copy_from_user(&log_addrs, parg, sizeof(log_addrs))) + return -EFAULT; + log_addrs.flags = 0; + mutex_lock(&adap->lock); + if (!adap->is_configuring && + (!log_addrs.num_log_addrs || !adap->is_configured) && + !cec_is_busy(adap, fh)) { + err = __cec_s_log_addrs(adap, &log_addrs, block); + if (!err) + log_addrs = adap->log_addrs; + } + mutex_unlock(&adap->lock); + if (err) + return err; + if (copy_to_user(parg, &log_addrs, sizeof(log_addrs))) + return -EFAULT; + return 0; +} + +static long cec_transmit(struct cec_adapter *adap, struct cec_fh *fh, + bool block, struct cec_msg __user *parg) +{ + struct cec_msg msg = {}; + long err = 0; + + if (!(adap->capabilities & CEC_CAP_TRANSMIT)) + return -ENOTTY; + if (copy_from_user(&msg, parg, sizeof(msg))) + return -EFAULT; + mutex_lock(&adap->lock); + if (!adap->is_configured) { + err = -ENONET; + } else if (cec_is_busy(adap, fh)) { + err = -EBUSY; + } else { + if (!block || !msg.reply) + fh = NULL; + err = cec_transmit_msg_fh(adap, &msg, fh, block); + } + mutex_unlock(&adap->lock); + if (err) + return err; + if (copy_to_user(parg, &msg, sizeof(msg))) + return -EFAULT; + return 0; +} + +/* Called by CEC_RECEIVE: wait for a message to arrive */ +static int cec_receive_msg(struct cec_fh *fh, struct cec_msg *msg, bool block) +{ + int res; + + do { + mutex_lock(&fh->lock); + /* Are there received messages queued up? */ + if (fh->queued_msgs) { + /* Yes, return the first one */ + struct cec_msg_entry *entry = + list_first_entry(&fh->msgs, + struct cec_msg_entry, list); + + list_del(&entry->list); + *msg = entry->msg; + kfree(entry); + fh->queued_msgs--; + mutex_unlock(&fh->lock); + return 0; + } + + /* No, return EAGAIN in non-blocking mode or wait */ + mutex_unlock(&fh->lock); + + /* Return when in non-blocking mode */ + if (!block) + return -EAGAIN; + + if (msg->timeout) { + /* The user specified a timeout */ + res = wait_event_interruptible_timeout(fh->wait, + fh->queued_msgs, + msecs_to_jiffies(msg->timeout)); + if (res == 0) + res = -ETIMEDOUT; + else if (res > 0) + res = 0; + } else { + /* Wait indefinitely */ + res = wait_event_interruptible(fh->wait, + fh->queued_msgs); + } + /* Exit on error, otherwise loop to get the new message */ + } while (!res); + return res; +} + +static long cec_receive(struct cec_adapter *adap, struct cec_fh *fh, + bool block, struct cec_msg __user *parg) +{ + struct cec_msg msg = {}; + long err = 0; + + if (copy_from_user(&msg, parg, sizeof(msg))) + return -EFAULT; + mutex_lock(&adap->lock); + if (!adap->is_configured) + err = -ENONET; + mutex_unlock(&adap->lock); + if (err) + return err; + + err = cec_receive_msg(fh, &msg, block); + if (err) + return err; + if (copy_to_user(parg, &msg, sizeof(msg))) + return -EFAULT; + return 0; +} + +static long cec_dqevent(struct cec_adapter *adap, struct cec_fh *fh, + bool block, struct cec_event __user *parg) +{ + struct cec_event *ev = NULL; + u64 ts = ~0ULL; + unsigned int i; + long err = 0; + + mutex_lock(&fh->lock); + while (!fh->pending_events && block) { + mutex_unlock(&fh->lock); + err = wait_event_interruptible(fh->wait, fh->pending_events); + if (err) + return err; + mutex_lock(&fh->lock); + } + + /* Find the oldest event */ + for (i = 0; i < CEC_NUM_EVENTS; i++) { + if (fh->pending_events & (1 << (i + 1)) && + fh->events[i].ts <= ts) { + ev = &fh->events[i]; + ts = ev->ts; + } + } + if (!ev) { + err = -EAGAIN; + goto unlock; + } + + if (copy_to_user(parg, ev, sizeof(*ev))) { + err = -EFAULT; + goto unlock; + } + + fh->pending_events &= ~(1 << ev->event); + +unlock: + mutex_unlock(&fh->lock); + return err; +} + +static long cec_g_mode(struct cec_adapter *adap, struct cec_fh *fh, + u32 __user *parg) +{ + u32 mode = fh->mode_initiator | fh->mode_follower; + + if (copy_to_user(parg, &mode, sizeof(mode))) + return -EFAULT; + return 0; +} + +static long cec_s_mode(struct cec_adapter *adap, struct cec_fh *fh, + u32 __user *parg) +{ + u32 mode; + u8 mode_initiator; + u8 mode_follower; + long err = 0; + + if (copy_from_user(&mode, parg, sizeof(mode))) + return -EFAULT; + if (mode & ~(CEC_MODE_INITIATOR_MSK | CEC_MODE_FOLLOWER_MSK)) + return -EINVAL; + + mode_initiator = mode & CEC_MODE_INITIATOR_MSK; + mode_follower = mode & CEC_MODE_FOLLOWER_MSK; + + if (mode_initiator > CEC_MODE_EXCL_INITIATOR || + mode_follower > CEC_MODE_MONITOR_ALL) + return -EINVAL; + + if (mode_follower == CEC_MODE_MONITOR_ALL && + !(adap->capabilities & CEC_CAP_MONITOR_ALL)) + return -EINVAL; + + /* Follower modes should always be able to send CEC messages */ + if ((mode_initiator == CEC_MODE_NO_INITIATOR || + !(adap->capabilities & CEC_CAP_TRANSMIT)) && + mode_follower >= CEC_MODE_FOLLOWER && + mode_follower <= CEC_MODE_EXCL_FOLLOWER_PASSTHRU) + return -EINVAL; + + /* Monitor modes require CEC_MODE_NO_INITIATOR */ + if (mode_initiator && mode_follower >= CEC_MODE_MONITOR) + return -EINVAL; + + /* Monitor modes require CAP_NET_ADMIN */ + if (mode_follower >= CEC_MODE_MONITOR && !capable(CAP_NET_ADMIN)) + return -EPERM; + + mutex_lock(&adap->lock); + /* + * You can't become exclusive follower if someone else already + * has that job. + */ + if ((mode_follower == CEC_MODE_EXCL_FOLLOWER || + mode_follower == CEC_MODE_EXCL_FOLLOWER_PASSTHRU) && + adap->cec_follower && adap->cec_follower != fh) + err = -EBUSY; + /* + * You can't become exclusive initiator if someone else already + * has that job. + */ + if (mode_initiator == CEC_MODE_EXCL_INITIATOR && + adap->cec_initiator && adap->cec_initiator != fh) + err = -EBUSY; + + if (!err) { + bool old_mon_all = fh->mode_follower == CEC_MODE_MONITOR_ALL; + bool new_mon_all = mode_follower == CEC_MODE_MONITOR_ALL; + + if (old_mon_all != new_mon_all) { + if (new_mon_all) + err = cec_monitor_all_cnt_inc(adap); + else + cec_monitor_all_cnt_dec(adap); + } + } + + if (err) { + mutex_unlock(&adap->lock); + return err; + } + + if (fh->mode_follower == CEC_MODE_FOLLOWER) + adap->follower_cnt--; + if (mode_follower == CEC_MODE_FOLLOWER) + adap->follower_cnt++; + if (mode_follower == CEC_MODE_EXCL_FOLLOWER || + mode_follower == CEC_MODE_EXCL_FOLLOWER_PASSTHRU) { + adap->passthrough = + mode_follower == CEC_MODE_EXCL_FOLLOWER_PASSTHRU; + adap->cec_follower = fh; + } else if (adap->cec_follower == fh) { + adap->passthrough = false; + adap->cec_follower = NULL; + } + if (mode_initiator == CEC_MODE_EXCL_INITIATOR) + adap->cec_initiator = fh; + else if (adap->cec_initiator == fh) + adap->cec_initiator = NULL; + fh->mode_initiator = mode_initiator; + fh->mode_follower = mode_follower; + mutex_unlock(&adap->lock); + return 0; +} + +static long cec_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct cec_devnode *devnode = cec_devnode_data(filp); + struct cec_fh *fh = filp->private_data; + struct cec_adapter *adap = fh->adap; + bool block = !(filp->f_flags & O_NONBLOCK); + void __user *parg = (void __user *)arg; + + if (!devnode->registered) + return -EIO; + + switch (cmd) { + case CEC_ADAP_G_CAPS: + return cec_adap_g_caps(adap, parg); + + case CEC_ADAP_G_PHYS_ADDR: + return cec_adap_g_phys_addr(adap, parg); + + case CEC_ADAP_S_PHYS_ADDR: + return cec_adap_s_phys_addr(adap, fh, block, parg); + + case CEC_ADAP_G_LOG_ADDRS: + return cec_adap_g_log_addrs(adap, parg); + + case CEC_ADAP_S_LOG_ADDRS: + return cec_adap_s_log_addrs(adap, fh, block, parg); + + case CEC_TRANSMIT: + return cec_transmit(adap, fh, block, parg); + + case CEC_RECEIVE: + return cec_receive(adap, fh, block, parg); + + case CEC_DQEVENT: + return cec_dqevent(adap, fh, block, parg); + + case CEC_G_MODE: + return cec_g_mode(adap, fh, parg); + + case CEC_S_MODE: + return cec_s_mode(adap, fh, parg); + + default: + return -ENOTTY; + } +} + +static int cec_open(struct inode *inode, struct file *filp) +{ + struct cec_devnode *devnode = + container_of(inode->i_cdev, struct cec_devnode, cdev); + struct cec_adapter *adap = to_cec_adapter(devnode); + struct cec_fh *fh = kzalloc(sizeof(*fh), GFP_KERNEL); + /* + * Initial events that are automatically sent when the cec device is + * opened. + */ + struct cec_event ev_state = { + .event = CEC_EVENT_STATE_CHANGE, + .flags = CEC_EVENT_FL_INITIAL_STATE, + }; + int err; + + if (!fh) + return -ENOMEM; + + INIT_LIST_HEAD(&fh->msgs); + INIT_LIST_HEAD(&fh->xfer_list); + mutex_init(&fh->lock); + init_waitqueue_head(&fh->wait); + + fh->mode_initiator = CEC_MODE_INITIATOR; + fh->adap = adap; + + err = cec_get_device(devnode); + if (err) { + kfree(fh); + return err; + } + + filp->private_data = fh; + + mutex_lock(&devnode->fhs_lock); + /* Queue up initial state events */ + ev_state.state_change.phys_addr = adap->phys_addr; + ev_state.state_change.log_addr_mask = adap->log_addrs.log_addr_mask; + cec_queue_event_fh(fh, &ev_state, 0); + + list_add(&fh->list, &devnode->fhs); + mutex_unlock(&devnode->fhs_lock); + + return 0; +} + +/* Override for the release function */ +static int cec_release(struct inode *inode, struct file *filp) +{ + struct cec_devnode *devnode = cec_devnode_data(filp); + struct cec_adapter *adap = to_cec_adapter(devnode); + struct cec_fh *fh = filp->private_data; + + mutex_lock(&adap->lock); + if (adap->cec_initiator == fh) + adap->cec_initiator = NULL; + if (adap->cec_follower == fh) { + adap->cec_follower = NULL; + adap->passthrough = false; + } + if (fh->mode_follower == CEC_MODE_FOLLOWER) + adap->follower_cnt--; + if (fh->mode_follower == CEC_MODE_MONITOR_ALL) + cec_monitor_all_cnt_dec(adap); + mutex_unlock(&adap->lock); + + mutex_lock(&devnode->fhs_lock); + list_del(&fh->list); + mutex_unlock(&devnode->fhs_lock); + + /* Unhook pending transmits from this filehandle. */ + mutex_lock(&adap->lock); + while (!list_empty(&fh->xfer_list)) { + struct cec_data *data = + list_first_entry(&fh->xfer_list, struct cec_data, xfer_list); + + data->blocking = false; + data->fh = NULL; + list_del(&data->xfer_list); + } + mutex_unlock(&adap->lock); + while (!list_empty(&fh->msgs)) { + struct cec_msg_entry *entry = + list_first_entry(&fh->msgs, struct cec_msg_entry, list); + + list_del(&entry->list); + kfree(entry); + } + kfree(fh); + + cec_put_device(devnode); + filp->private_data = NULL; + return 0; +} + +const struct file_operations cec_devnode_fops = { + .owner = THIS_MODULE, + .open = cec_open, + .unlocked_ioctl = cec_ioctl, + .release = cec_release, + .poll = cec_poll, + .llseek = no_llseek, +}; diff --git a/drivers/staging/media/cec/cec-core.c b/drivers/staging/media/cec/cec-core.c new file mode 100644 index 000000000000..61a1e69a902a --- /dev/null +++ b/drivers/staging/media/cec/cec-core.c @@ -0,0 +1,409 @@ +/* + * cec-core.c - HDMI Consumer Electronics Control framework - Core + * + * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * This program is free software; you may redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/kmod.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/string.h> +#include <linux/types.h> + +#include "cec-priv.h" + +#define CEC_NUM_DEVICES 256 +#define CEC_NAME "cec" + +int cec_debug; +module_param_named(debug, cec_debug, int, 0644); +MODULE_PARM_DESC(debug, "debug level (0-2)"); + +static dev_t cec_dev_t; + +/* Active devices */ +static DEFINE_MUTEX(cec_devnode_lock); +static DECLARE_BITMAP(cec_devnode_nums, CEC_NUM_DEVICES); + +static struct dentry *top_cec_dir; + +/* dev to cec_devnode */ +#define to_cec_devnode(cd) container_of(cd, struct cec_devnode, dev) + +int cec_get_device(struct cec_devnode *devnode) +{ + /* + * Check if the cec device is available. This needs to be done with + * the cec_devnode_lock held to prevent an open/unregister race: + * without the lock, the device could be unregistered and freed between + * the devnode->registered check and get_device() calls, leading to + * a crash. + */ + mutex_lock(&cec_devnode_lock); + /* + * return ENXIO if the cec device has been removed + * already or if it is not registered anymore. + */ + if (!devnode->registered) { + mutex_unlock(&cec_devnode_lock); + return -ENXIO; + } + /* and increase the device refcount */ + get_device(&devnode->dev); + mutex_unlock(&cec_devnode_lock); + return 0; +} + +void cec_put_device(struct cec_devnode *devnode) +{ + mutex_lock(&cec_devnode_lock); + put_device(&devnode->dev); + mutex_unlock(&cec_devnode_lock); +} + +/* Called when the last user of the cec device exits. */ +static void cec_devnode_release(struct device *cd) +{ + struct cec_devnode *devnode = to_cec_devnode(cd); + + mutex_lock(&cec_devnode_lock); + + /* Mark device node number as free */ + clear_bit(devnode->minor, cec_devnode_nums); + + mutex_unlock(&cec_devnode_lock); + cec_delete_adapter(to_cec_adapter(devnode)); +} + +static struct bus_type cec_bus_type = { + .name = CEC_NAME, +}; + +/* + * Register a cec device node + * + * The registration code assigns minor numbers and registers the new device node + * with the kernel. An error is returned if no free minor number can be found, + * or if the registration of the device node fails. + * + * Zero is returned on success. + * + * Note that if the cec_devnode_register call fails, the release() callback of + * the cec_devnode structure is *not* called, so the caller is responsible for + * freeing any data. + */ +static int __must_check cec_devnode_register(struct cec_devnode *devnode, + struct module *owner) +{ + int minor; + int ret; + + /* Initialization */ + INIT_LIST_HEAD(&devnode->fhs); + mutex_init(&devnode->fhs_lock); + + /* Part 1: Find a free minor number */ + mutex_lock(&cec_devnode_lock); + minor = find_next_zero_bit(cec_devnode_nums, CEC_NUM_DEVICES, 0); + if (minor == CEC_NUM_DEVICES) { + mutex_unlock(&cec_devnode_lock); + pr_err("could not get a free minor\n"); + return -ENFILE; + } + + set_bit(minor, cec_devnode_nums); + mutex_unlock(&cec_devnode_lock); + + devnode->minor = minor; + devnode->dev.bus = &cec_bus_type; + devnode->dev.devt = MKDEV(MAJOR(cec_dev_t), minor); + devnode->dev.release = cec_devnode_release; + devnode->dev.parent = devnode->parent; + dev_set_name(&devnode->dev, "cec%d", devnode->minor); + device_initialize(&devnode->dev); + + /* Part 2: Initialize and register the character device */ + cdev_init(&devnode->cdev, &cec_devnode_fops); + devnode->cdev.kobj.parent = &devnode->dev.kobj; + devnode->cdev.owner = owner; + + ret = cdev_add(&devnode->cdev, devnode->dev.devt, 1); + if (ret < 0) { + pr_err("%s: cdev_add failed\n", __func__); + goto clr_bit; + } + + ret = device_add(&devnode->dev); + if (ret) + goto cdev_del; + + devnode->registered = true; + return 0; + +cdev_del: + cdev_del(&devnode->cdev); +clr_bit: + clear_bit(devnode->minor, cec_devnode_nums); + return ret; +} + +/* + * Unregister a cec device node + * + * This unregisters the passed device. Future open calls will be met with + * errors. + * + * This function can safely be called if the device node has never been + * registered or has already been unregistered. + */ +static void cec_devnode_unregister(struct cec_devnode *devnode) +{ + struct cec_fh *fh; + + /* Check if devnode was never registered or already unregistered */ + if (!devnode->registered || devnode->unregistered) + return; + + mutex_lock(&devnode->fhs_lock); + list_for_each_entry(fh, &devnode->fhs, list) + wake_up_interruptible(&fh->wait); + mutex_unlock(&devnode->fhs_lock); + + devnode->registered = false; + devnode->unregistered = true; + device_del(&devnode->dev); + cdev_del(&devnode->cdev); + put_device(&devnode->dev); +} + +struct cec_adapter *cec_allocate_adapter(const struct cec_adap_ops *ops, + void *priv, const char *name, u32 caps, + u8 available_las, struct device *parent) +{ + struct cec_adapter *adap; + int res; + + if (WARN_ON(!parent)) + return ERR_PTR(-EINVAL); + if (WARN_ON(!caps)) + return ERR_PTR(-EINVAL); + if (WARN_ON(!ops)) + return ERR_PTR(-EINVAL); + if (WARN_ON(!available_las || available_las > CEC_MAX_LOG_ADDRS)) + return ERR_PTR(-EINVAL); + adap = kzalloc(sizeof(*adap), GFP_KERNEL); + if (!adap) + return ERR_PTR(-ENOMEM); + adap->owner = parent->driver->owner; + adap->devnode.parent = parent; + strlcpy(adap->name, name, sizeof(adap->name)); + adap->phys_addr = CEC_PHYS_ADDR_INVALID; + adap->log_addrs.cec_version = CEC_OP_CEC_VERSION_2_0; + adap->log_addrs.vendor_id = CEC_VENDOR_ID_NONE; + adap->capabilities = caps; + adap->available_log_addrs = available_las; + adap->sequence = 0; + adap->ops = ops; + adap->priv = priv; + memset(adap->phys_addrs, 0xff, sizeof(adap->phys_addrs)); + mutex_init(&adap->lock); + INIT_LIST_HEAD(&adap->transmit_queue); + INIT_LIST_HEAD(&adap->wait_queue); + init_waitqueue_head(&adap->kthread_waitq); + + adap->kthread = kthread_run(cec_thread_func, adap, "cec-%s", name); + if (IS_ERR(adap->kthread)) { + pr_err("cec-%s: kernel_thread() failed\n", name); + res = PTR_ERR(adap->kthread); + kfree(adap); + return ERR_PTR(res); + } + + if (!(caps & CEC_CAP_RC)) + return adap; + +#if IS_ENABLED(CONFIG_RC_CORE) + /* Prepare the RC input device */ + adap->rc = rc_allocate_device(); + if (!adap->rc) { + pr_err("cec-%s: failed to allocate memory for rc_dev\n", + name); + kthread_stop(adap->kthread); + kfree(adap); + return ERR_PTR(-ENOMEM); + } + + snprintf(adap->input_name, sizeof(adap->input_name), + "RC for %s", name); + snprintf(adap->input_phys, sizeof(adap->input_phys), + "%s/input0", name); + + adap->rc->input_name = adap->input_name; + adap->rc->input_phys = adap->input_phys; + adap->rc->input_id.bustype = BUS_CEC; + adap->rc->input_id.vendor = 0; + adap->rc->input_id.product = 0; + adap->rc->input_id.version = 1; + adap->rc->dev.parent = parent; + adap->rc->driver_type = RC_DRIVER_SCANCODE; + adap->rc->driver_name = CEC_NAME; + adap->rc->allowed_protocols = RC_BIT_CEC; + adap->rc->priv = adap; + adap->rc->map_name = RC_MAP_CEC; + adap->rc->timeout = MS_TO_NS(100); +#else + adap->capabilities &= ~CEC_CAP_RC; +#endif + return adap; +} +EXPORT_SYMBOL_GPL(cec_allocate_adapter); + +int cec_register_adapter(struct cec_adapter *adap) +{ + int res; + + if (IS_ERR_OR_NULL(adap)) + return 0; + +#if IS_ENABLED(CONFIG_RC_CORE) + if (adap->capabilities & CEC_CAP_RC) { + res = rc_register_device(adap->rc); + + if (res) { + pr_err("cec-%s: failed to prepare input device\n", + adap->name); + rc_free_device(adap->rc); + adap->rc = NULL; + return res; + } + } +#endif + + res = cec_devnode_register(&adap->devnode, adap->owner); + if (res) { +#if IS_ENABLED(CONFIG_RC_CORE) + /* Note: rc_unregister also calls rc_free */ + rc_unregister_device(adap->rc); + adap->rc = NULL; +#endif + return res; + } + + dev_set_drvdata(&adap->devnode.dev, adap); +#ifdef CONFIG_MEDIA_CEC_DEBUG + if (!top_cec_dir) + return 0; + + adap->cec_dir = debugfs_create_dir(dev_name(&adap->devnode.dev), top_cec_dir); + if (IS_ERR_OR_NULL(adap->cec_dir)) { + pr_warn("cec-%s: Failed to create debugfs dir\n", adap->name); + return 0; + } + adap->status_file = debugfs_create_devm_seqfile(&adap->devnode.dev, + "status", adap->cec_dir, cec_adap_status); + if (IS_ERR_OR_NULL(adap->status_file)) { + pr_warn("cec-%s: Failed to create status file\n", adap->name); + debugfs_remove_recursive(adap->cec_dir); + adap->cec_dir = NULL; + } +#endif + return 0; +} +EXPORT_SYMBOL_GPL(cec_register_adapter); + +void cec_unregister_adapter(struct cec_adapter *adap) +{ + if (IS_ERR_OR_NULL(adap)) + return; + +#if IS_ENABLED(CONFIG_RC_CORE) + /* Note: rc_unregister also calls rc_free */ + rc_unregister_device(adap->rc); + adap->rc = NULL; +#endif + debugfs_remove_recursive(adap->cec_dir); + cec_devnode_unregister(&adap->devnode); +} +EXPORT_SYMBOL_GPL(cec_unregister_adapter); + +void cec_delete_adapter(struct cec_adapter *adap) +{ + if (IS_ERR_OR_NULL(adap)) + return; + mutex_lock(&adap->lock); + __cec_s_phys_addr(adap, CEC_PHYS_ADDR_INVALID, false); + mutex_unlock(&adap->lock); + kthread_stop(adap->kthread); + if (adap->kthread_config) + kthread_stop(adap->kthread_config); +#if IS_ENABLED(CONFIG_RC_CORE) + if (adap->rc) + rc_free_device(adap->rc); +#endif + kfree(adap); +} +EXPORT_SYMBOL_GPL(cec_delete_adapter); + +/* + * Initialise cec for linux + */ +static int __init cec_devnode_init(void) +{ + int ret; + + pr_info("Linux cec interface: v0.10\n"); + ret = alloc_chrdev_region(&cec_dev_t, 0, CEC_NUM_DEVICES, + CEC_NAME); + if (ret < 0) { + pr_warn("cec: unable to allocate major\n"); + return ret; + } + +#ifdef CONFIG_MEDIA_CEC_DEBUG + top_cec_dir = debugfs_create_dir("cec", NULL); + if (IS_ERR_OR_NULL(top_cec_dir)) { + pr_warn("cec: Failed to create debugfs cec dir\n"); + top_cec_dir = NULL; + } +#endif + + ret = bus_register(&cec_bus_type); + if (ret < 0) { + unregister_chrdev_region(cec_dev_t, CEC_NUM_DEVICES); + pr_warn("cec: bus_register failed\n"); + return -EIO; + } + + return 0; +} + +static void __exit cec_devnode_exit(void) +{ + debugfs_remove_recursive(top_cec_dir); + bus_unregister(&cec_bus_type); + unregister_chrdev_region(cec_dev_t, CEC_NUM_DEVICES); +} + +subsys_initcall(cec_devnode_init); +module_exit(cec_devnode_exit) + +MODULE_AUTHOR("Hans Verkuil <hans.verkuil@cisco.com>"); +MODULE_DESCRIPTION("Device node registration for cec drivers"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/media/cec/cec-priv.h b/drivers/staging/media/cec/cec-priv.h new file mode 100644 index 000000000000..70767a7900f2 --- /dev/null +++ b/drivers/staging/media/cec/cec-priv.h @@ -0,0 +1,56 @@ +/* + * cec-priv.h - HDMI Consumer Electronics Control internal header + * + * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * This program is free software; you may redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _CEC_PRIV_H +#define _CEC_PRIV_H + +#include <linux/cec-funcs.h> +#include <media/cec.h> + +#define dprintk(lvl, fmt, arg...) \ + do { \ + if (lvl <= cec_debug) \ + pr_info("cec-%s: " fmt, adap->name, ## arg); \ + } while (0) + +/* devnode to cec_adapter */ +#define to_cec_adapter(node) container_of(node, struct cec_adapter, devnode) + +/* cec-core.c */ +extern int cec_debug; +int cec_get_device(struct cec_devnode *devnode); +void cec_put_device(struct cec_devnode *devnode); + +/* cec-adap.c */ +int cec_monitor_all_cnt_inc(struct cec_adapter *adap); +void cec_monitor_all_cnt_dec(struct cec_adapter *adap); +int cec_adap_status(struct seq_file *file, void *priv); +int cec_thread_func(void *_adap); +void __cec_s_phys_addr(struct cec_adapter *adap, u16 phys_addr, bool block); +int __cec_s_log_addrs(struct cec_adapter *adap, + struct cec_log_addrs *log_addrs, bool block); +int cec_transmit_msg_fh(struct cec_adapter *adap, struct cec_msg *msg, + struct cec_fh *fh, bool block); +void cec_queue_event_fh(struct cec_fh *fh, + const struct cec_event *new_ev, u64 ts); + +/* cec-api.c */ +extern const struct file_operations cec_devnode_fops; + +#endif diff --git a/drivers/staging/media/mn88472/Kconfig b/drivers/staging/media/mn88472/Kconfig deleted file mode 100644 index a85c90a60bce..000000000000 --- a/drivers/staging/media/mn88472/Kconfig +++ /dev/null @@ -1,7 +0,0 @@ -config DVB_MN88472 - tristate "Panasonic MN88472" - depends on DVB_CORE && I2C - select REGMAP_I2C - default m if !MEDIA_SUBDRV_AUTOSELECT - help - Say Y when you want to support this frontend. diff --git a/drivers/staging/media/mn88472/Makefile b/drivers/staging/media/mn88472/Makefile deleted file mode 100644 index 5987b7e6d82a..000000000000 --- a/drivers/staging/media/mn88472/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -obj-$(CONFIG_DVB_MN88472) += mn88472.o - -ccflags-y += -Idrivers/media/dvb-core/ -ccflags-y += -Idrivers/media/dvb-frontends/ -ccflags-y += -Idrivers/media/tuners/ diff --git a/drivers/staging/media/mn88472/TODO b/drivers/staging/media/mn88472/TODO deleted file mode 100644 index b90a14be3beb..000000000000 --- a/drivers/staging/media/mn88472/TODO +++ /dev/null @@ -1,21 +0,0 @@ -Driver general quality is not good enough for mainline. Also, other -device drivers (USB-bridge, tuner) needed for Astrometa receiver in -question could need some changes. However, if that driver is mainlined -due to some other device than Astrometa, unrelated TODOs could be -skipped. In that case rtl28xxu driver needs module parameter to prevent -driver loading. - -Required TODOs: -* missing lock flags -* I2C errors -* tuner sensitivity - -*Do not* send any patch fixing checkpatch.pl issues. Currently it passes -checkpatch.pl tests. I don't want waste my time to review this kind of -trivial stuff. *Do not* add missing register I/O error checks. Those are -missing for the reason it is much easier to compare I2C data sniffs when -there is less lines. Those error checks are about the last thing to be added. - -Patches should be submitted to: -linux-media@vger.kernel.org and Antti Palosaari <crope@iki.fi> - diff --git a/drivers/staging/media/mx2/Kconfig b/drivers/staging/media/mx2/Kconfig deleted file mode 100644 index beaa885cf104..000000000000 --- a/drivers/staging/media/mx2/Kconfig +++ /dev/null @@ -1,15 +0,0 @@ -config VIDEO_MX2 - tristate "i.MX27 Camera Sensor Interface driver" - depends on VIDEO_DEV && SOC_CAMERA - depends on SOC_IMX27 || COMPILE_TEST - depends on HAS_DMA - select VIDEOBUF2_DMA_CONTIG - ---help--- - This is a v4l2 driver for the i.MX27 Camera Sensor Interface - - This driver is deprecated: it should become a stand-alone driver - instead of using the soc-camera framework. - - Unless someone is willing to take this on (unlikely with such - ancient hardware) it is going to be removed from the kernel - soon. diff --git a/drivers/staging/media/mx2/Makefile b/drivers/staging/media/mx2/Makefile deleted file mode 100644 index fc5b2826a558..000000000000 --- a/drivers/staging/media/mx2/Makefile +++ /dev/null @@ -1,3 +0,0 @@ -# Makefile for i.MX27 Camera Sensor driver - -obj-$(CONFIG_VIDEO_MX2) += mx2_camera.o diff --git a/drivers/staging/media/mx2/TODO b/drivers/staging/media/mx2/TODO deleted file mode 100644 index bc68fa443a3e..000000000000 --- a/drivers/staging/media/mx2/TODO +++ /dev/null @@ -1,10 +0,0 @@ -This driver is deprecated: it should become a stand-alone driver instead of -using the soc-camera framework. - -Unless someone is willing to take this on (unlikely with such ancient -hardware) it is going to be removed from the kernel soon. - -Note that trivial patches will not be accepted anymore, only a full conversion. - -If you want to convert this driver, please contact the linux-media mailinglist -(see http://linuxtv.org/lists.php). diff --git a/drivers/staging/media/mx2/mx2_camera.c b/drivers/staging/media/mx2/mx2_camera.c deleted file mode 100644 index 48dd5b7851b5..000000000000 --- a/drivers/staging/media/mx2/mx2_camera.c +++ /dev/null @@ -1,1636 +0,0 @@ -/* - * V4L2 Driver for i.MX27 camera host - * - * Copyright (C) 2008, Sascha Hauer, Pengutronix - * Copyright (C) 2010, Baruch Siach, Orex Computed Radiography - * Copyright (C) 2012, Javier Martin, Vista Silicon S.L. - * - * 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/init.h> -#include <linux/module.h> -#include <linux/io.h> -#include <linux/delay.h> -#include <linux/slab.h> -#include <linux/dma-mapping.h> -#include <linux/errno.h> -#include <linux/fs.h> -#include <linux/gcd.h> -#include <linux/interrupt.h> -#include <linux/kernel.h> -#include <linux/math64.h> -#include <linux/mm.h> -#include <linux/moduleparam.h> -#include <linux/time.h> -#include <linux/device.h> -#include <linux/platform_device.h> -#include <linux/clk.h> - -#include <media/v4l2-common.h> -#include <media/v4l2-dev.h> -#include <media/videobuf2-v4l2.h> -#include <media/videobuf2-dma-contig.h> -#include <media/soc_camera.h> -#include <media/drv-intf/soc_mediabus.h> - -#include <linux/videodev2.h> - -#include <linux/platform_data/media/camera-mx2.h> - -#include <asm/dma.h> - -#define MX2_CAM_DRV_NAME "mx2-camera" -#define MX2_CAM_VERSION "0.0.6" -#define MX2_CAM_DRIVER_DESCRIPTION "i.MX2x_Camera" - -/* reset values */ -#define CSICR1_RESET_VAL 0x40000800 -#define CSICR2_RESET_VAL 0x0 -#define CSICR3_RESET_VAL 0x0 - -/* csi control reg 1 */ -#define CSICR1_SWAP16_EN (1 << 31) -#define CSICR1_EXT_VSYNC (1 << 30) -#define CSICR1_EOF_INTEN (1 << 29) -#define CSICR1_PRP_IF_EN (1 << 28) -#define CSICR1_CCIR_MODE (1 << 27) -#define CSICR1_COF_INTEN (1 << 26) -#define CSICR1_SF_OR_INTEN (1 << 25) -#define CSICR1_RF_OR_INTEN (1 << 24) -#define CSICR1_STATFF_LEVEL (3 << 22) -#define CSICR1_STATFF_INTEN (1 << 21) -#define CSICR1_RXFF_LEVEL(l) (((l) & 3) << 19) -#define CSICR1_RXFF_INTEN (1 << 18) -#define CSICR1_SOF_POL (1 << 17) -#define CSICR1_SOF_INTEN (1 << 16) -#define CSICR1_MCLKDIV(d) (((d) & 0xF) << 12) -#define CSICR1_HSYNC_POL (1 << 11) -#define CSICR1_CCIR_EN (1 << 10) -#define CSICR1_MCLKEN (1 << 9) -#define CSICR1_FCC (1 << 8) -#define CSICR1_PACK_DIR (1 << 7) -#define CSICR1_CLR_STATFIFO (1 << 6) -#define CSICR1_CLR_RXFIFO (1 << 5) -#define CSICR1_GCLK_MODE (1 << 4) -#define CSICR1_INV_DATA (1 << 3) -#define CSICR1_INV_PCLK (1 << 2) -#define CSICR1_REDGE (1 << 1) -#define CSICR1_FMT_MASK (CSICR1_PACK_DIR | CSICR1_SWAP16_EN) - -#define SHIFT_STATFF_LEVEL 22 -#define SHIFT_RXFF_LEVEL 19 -#define SHIFT_MCLKDIV 12 - -#define SHIFT_FRMCNT 16 - -#define CSICR1 0x00 -#define CSICR2 0x04 -#define CSISR 0x08 -#define CSISTATFIFO 0x0c -#define CSIRFIFO 0x10 -#define CSIRXCNT 0x14 -#define CSICR3 0x1c -#define CSIDMASA_STATFIFO 0x20 -#define CSIDMATA_STATFIFO 0x24 -#define CSIDMASA_FB1 0x28 -#define CSIDMASA_FB2 0x2c -#define CSIFBUF_PARA 0x30 -#define CSIIMAG_PARA 0x34 - -/* EMMA PrP */ -#define PRP_CNTL 0x00 -#define PRP_INTR_CNTL 0x04 -#define PRP_INTRSTATUS 0x08 -#define PRP_SOURCE_Y_PTR 0x0c -#define PRP_SOURCE_CB_PTR 0x10 -#define PRP_SOURCE_CR_PTR 0x14 -#define PRP_DEST_RGB1_PTR 0x18 -#define PRP_DEST_RGB2_PTR 0x1c -#define PRP_DEST_Y_PTR 0x20 -#define PRP_DEST_CB_PTR 0x24 -#define PRP_DEST_CR_PTR 0x28 -#define PRP_SRC_FRAME_SIZE 0x2c -#define PRP_DEST_CH1_LINE_STRIDE 0x30 -#define PRP_SRC_PIXEL_FORMAT_CNTL 0x34 -#define PRP_CH1_PIXEL_FORMAT_CNTL 0x38 -#define PRP_CH1_OUT_IMAGE_SIZE 0x3c -#define PRP_CH2_OUT_IMAGE_SIZE 0x40 -#define PRP_SRC_LINE_STRIDE 0x44 -#define PRP_CSC_COEF_012 0x48 -#define PRP_CSC_COEF_345 0x4c -#define PRP_CSC_COEF_678 0x50 -#define PRP_CH1_RZ_HORI_COEF1 0x54 -#define PRP_CH1_RZ_HORI_COEF2 0x58 -#define PRP_CH1_RZ_HORI_VALID 0x5c -#define PRP_CH1_RZ_VERT_COEF1 0x60 -#define PRP_CH1_RZ_VERT_COEF2 0x64 -#define PRP_CH1_RZ_VERT_VALID 0x68 -#define PRP_CH2_RZ_HORI_COEF1 0x6c -#define PRP_CH2_RZ_HORI_COEF2 0x70 -#define PRP_CH2_RZ_HORI_VALID 0x74 -#define PRP_CH2_RZ_VERT_COEF1 0x78 -#define PRP_CH2_RZ_VERT_COEF2 0x7c -#define PRP_CH2_RZ_VERT_VALID 0x80 - -#define PRP_CNTL_CH1EN (1 << 0) -#define PRP_CNTL_CH2EN (1 << 1) -#define PRP_CNTL_CSIEN (1 << 2) -#define PRP_CNTL_DATA_IN_YUV420 (0 << 3) -#define PRP_CNTL_DATA_IN_YUV422 (1 << 3) -#define PRP_CNTL_DATA_IN_RGB16 (2 << 3) -#define PRP_CNTL_DATA_IN_RGB32 (3 << 3) -#define PRP_CNTL_CH1_OUT_RGB8 (0 << 5) -#define PRP_CNTL_CH1_OUT_RGB16 (1 << 5) -#define PRP_CNTL_CH1_OUT_RGB32 (2 << 5) -#define PRP_CNTL_CH1_OUT_YUV422 (3 << 5) -#define PRP_CNTL_CH2_OUT_YUV420 (0 << 7) -#define PRP_CNTL_CH2_OUT_YUV422 (1 << 7) -#define PRP_CNTL_CH2_OUT_YUV444 (2 << 7) -#define PRP_CNTL_CH1_LEN (1 << 9) -#define PRP_CNTL_CH2_LEN (1 << 10) -#define PRP_CNTL_SKIP_FRAME (1 << 11) -#define PRP_CNTL_SWRST (1 << 12) -#define PRP_CNTL_CLKEN (1 << 13) -#define PRP_CNTL_WEN (1 << 14) -#define PRP_CNTL_CH1BYP (1 << 15) -#define PRP_CNTL_IN_TSKIP(x) ((x) << 16) -#define PRP_CNTL_CH1_TSKIP(x) ((x) << 19) -#define PRP_CNTL_CH2_TSKIP(x) ((x) << 22) -#define PRP_CNTL_INPUT_FIFO_LEVEL(x) ((x) << 25) -#define PRP_CNTL_RZ_FIFO_LEVEL(x) ((x) << 27) -#define PRP_CNTL_CH2B1EN (1 << 29) -#define PRP_CNTL_CH2B2EN (1 << 30) -#define PRP_CNTL_CH2FEN (1 << 31) - -/* IRQ Enable and status register */ -#define PRP_INTR_RDERR (1 << 0) -#define PRP_INTR_CH1WERR (1 << 1) -#define PRP_INTR_CH2WERR (1 << 2) -#define PRP_INTR_CH1FC (1 << 3) -#define PRP_INTR_CH2FC (1 << 5) -#define PRP_INTR_LBOVF (1 << 7) -#define PRP_INTR_CH2OVF (1 << 8) - -/* Resizing registers */ -#define PRP_RZ_VALID_TBL_LEN(x) ((x) << 24) -#define PRP_RZ_VALID_BILINEAR (1 << 31) - -#define MAX_VIDEO_MEM 16 - -#define RESIZE_NUM_MIN 1 -#define RESIZE_NUM_MAX 20 -#define BC_COEF 3 -#define SZ_COEF (1 << BC_COEF) - -#define RESIZE_DIR_H 0 -#define RESIZE_DIR_V 1 - -#define RESIZE_ALGO_BILINEAR 0 -#define RESIZE_ALGO_AVERAGING 1 - -struct mx2_prp_cfg { - int channel; - u32 in_fmt; - u32 out_fmt; - u32 src_pixel; - u32 ch1_pixel; - u32 irq_flags; - u32 csicr1; -}; - -/* prp resizing parameters */ -struct emma_prp_resize { - int algo; /* type of algorithm used */ - int len; /* number of coefficients */ - unsigned char s[RESIZE_NUM_MAX]; /* table of coefficients */ -}; - -/* prp configuration for a client-host fmt pair */ -struct mx2_fmt_cfg { - u32 in_fmt; - u32 out_fmt; - struct mx2_prp_cfg cfg; -}; - -struct mx2_buf_internal { - struct list_head queue; - int bufnum; - bool discard; -}; - -/* buffer for one video frame */ -struct mx2_buffer { - /* common v4l buffer stuff -- must be first */ - struct vb2_v4l2_buffer vb; - struct mx2_buf_internal internal; -}; - -enum mx2_camera_type { - IMX27_CAMERA, -}; - -struct mx2_camera_dev { - struct device *dev; - struct soc_camera_host soc_host; - struct clk *clk_emma_ahb, *clk_emma_ipg; - struct clk *clk_csi_ahb, *clk_csi_per; - - void __iomem *base_csi, *base_emma; - - struct mx2_camera_platform_data *pdata; - unsigned long platform_flags; - - struct list_head capture; - struct list_head active_bufs; - struct list_head discard; - - spinlock_t lock; - - int dma; - struct mx2_buffer *active; - struct mx2_buffer *fb1_active; - struct mx2_buffer *fb2_active; - - u32 csicr1; - enum mx2_camera_type devtype; - - struct mx2_buf_internal buf_discard[2]; - void *discard_buffer; - dma_addr_t discard_buffer_dma; - size_t discard_size; - struct mx2_fmt_cfg *emma_prp; - struct emma_prp_resize resizing[2]; - unsigned int s_width, s_height; - u32 frame_count; - struct vb2_alloc_ctx *alloc_ctx; -}; - -static struct platform_device_id mx2_camera_devtype[] = { - { - .name = "imx27-camera", - .driver_data = IMX27_CAMERA, - }, { - /* sentinel */ - } -}; -MODULE_DEVICE_TABLE(platform, mx2_camera_devtype); - -static struct mx2_buffer *mx2_ibuf_to_buf(struct mx2_buf_internal *int_buf) -{ - return container_of(int_buf, struct mx2_buffer, internal); -} - -static struct mx2_fmt_cfg mx27_emma_prp_table[] = { - /* - * This is a generic configuration which is valid for most - * prp input-output format combinations. - * We set the incoming and outgoing pixelformat to a - * 16 Bit wide format and adjust the bytesperline - * accordingly. With this configuration the inputdata - * will not be changed by the emma and could be any type - * of 16 Bit Pixelformat. - */ - { - .in_fmt = 0, - .out_fmt = 0, - .cfg = { - .channel = 1, - .in_fmt = PRP_CNTL_DATA_IN_RGB16, - .out_fmt = PRP_CNTL_CH1_OUT_RGB16, - .src_pixel = 0x2ca00565, /* RGB565 */ - .ch1_pixel = 0x2ca00565, /* RGB565 */ - .irq_flags = PRP_INTR_RDERR | PRP_INTR_CH1WERR | - PRP_INTR_CH1FC | PRP_INTR_LBOVF, - .csicr1 = 0, - } - }, - { - .in_fmt = MEDIA_BUS_FMT_UYVY8_2X8, - .out_fmt = V4L2_PIX_FMT_YUYV, - .cfg = { - .channel = 1, - .in_fmt = PRP_CNTL_DATA_IN_YUV422, - .out_fmt = PRP_CNTL_CH1_OUT_YUV422, - .src_pixel = 0x22000888, /* YUV422 (YUYV) */ - .ch1_pixel = 0x62000888, /* YUV422 (YUYV) */ - .irq_flags = PRP_INTR_RDERR | PRP_INTR_CH1WERR | - PRP_INTR_CH1FC | PRP_INTR_LBOVF, - .csicr1 = CSICR1_SWAP16_EN, - } - }, - { - .in_fmt = MEDIA_BUS_FMT_YUYV8_2X8, - .out_fmt = V4L2_PIX_FMT_YUYV, - .cfg = { - .channel = 1, - .in_fmt = PRP_CNTL_DATA_IN_YUV422, - .out_fmt = PRP_CNTL_CH1_OUT_YUV422, - .src_pixel = 0x22000888, /* YUV422 (YUYV) */ - .ch1_pixel = 0x62000888, /* YUV422 (YUYV) */ - .irq_flags = PRP_INTR_RDERR | PRP_INTR_CH1WERR | - PRP_INTR_CH1FC | PRP_INTR_LBOVF, - .csicr1 = CSICR1_PACK_DIR, - } - }, - { - .in_fmt = MEDIA_BUS_FMT_YUYV8_2X8, - .out_fmt = V4L2_PIX_FMT_YUV420, - .cfg = { - .channel = 2, - .in_fmt = PRP_CNTL_DATA_IN_YUV422, - .out_fmt = PRP_CNTL_CH2_OUT_YUV420, - .src_pixel = 0x22000888, /* YUV422 (YUYV) */ - .irq_flags = PRP_INTR_RDERR | PRP_INTR_CH2WERR | - PRP_INTR_CH2FC | PRP_INTR_LBOVF | - PRP_INTR_CH2OVF, - .csicr1 = CSICR1_PACK_DIR, - } - }, - { - .in_fmt = MEDIA_BUS_FMT_UYVY8_2X8, - .out_fmt = V4L2_PIX_FMT_YUV420, - .cfg = { - .channel = 2, - .in_fmt = PRP_CNTL_DATA_IN_YUV422, - .out_fmt = PRP_CNTL_CH2_OUT_YUV420, - .src_pixel = 0x22000888, /* YUV422 (YUYV) */ - .irq_flags = PRP_INTR_RDERR | PRP_INTR_CH2WERR | - PRP_INTR_CH2FC | PRP_INTR_LBOVF | - PRP_INTR_CH2OVF, - .csicr1 = CSICR1_SWAP16_EN, - } - }, -}; - -static struct mx2_fmt_cfg *mx27_emma_prp_get_format(u32 in_fmt, u32 out_fmt) -{ - int i; - - for (i = 1; i < ARRAY_SIZE(mx27_emma_prp_table); i++) - if ((mx27_emma_prp_table[i].in_fmt == in_fmt) && - (mx27_emma_prp_table[i].out_fmt == out_fmt)) { - return &mx27_emma_prp_table[i]; - } - /* If no match return the most generic configuration */ - return &mx27_emma_prp_table[0]; -}; - -static void mx27_update_emma_buf(struct mx2_camera_dev *pcdev, - unsigned long phys, int bufnum) -{ - struct mx2_fmt_cfg *prp = pcdev->emma_prp; - - if (prp->cfg.channel == 1) { - writel(phys, pcdev->base_emma + - PRP_DEST_RGB1_PTR + 4 * bufnum); - } else { - writel(phys, pcdev->base_emma + - PRP_DEST_Y_PTR - 0x14 * bufnum); - if (prp->out_fmt == V4L2_PIX_FMT_YUV420) { - u32 imgsize = pcdev->soc_host.icd->user_height * - pcdev->soc_host.icd->user_width; - - writel(phys + imgsize, pcdev->base_emma + - PRP_DEST_CB_PTR - 0x14 * bufnum); - writel(phys + ((5 * imgsize) / 4), pcdev->base_emma + - PRP_DEST_CR_PTR - 0x14 * bufnum); - } - } -} - -static void mx2_camera_deactivate(struct mx2_camera_dev *pcdev) -{ - clk_disable_unprepare(pcdev->clk_csi_ahb); - clk_disable_unprepare(pcdev->clk_csi_per); - writel(0, pcdev->base_csi + CSICR1); - writel(0, pcdev->base_emma + PRP_CNTL); -} - -static int mx2_camera_add_device(struct soc_camera_device *icd) -{ - dev_info(icd->parent, "Camera driver attached to camera %d\n", - icd->devnum); - - return 0; -} - -static void mx2_camera_remove_device(struct soc_camera_device *icd) -{ - dev_info(icd->parent, "Camera driver detached from camera %d\n", - icd->devnum); -} - -/* - * The following two functions absolutely depend on the fact, that - * there can be only one camera on mx2 camera sensor interface - */ -static int mx2_camera_clock_start(struct soc_camera_host *ici) -{ - struct mx2_camera_dev *pcdev = ici->priv; - int ret; - u32 csicr1; - - ret = clk_prepare_enable(pcdev->clk_csi_ahb); - if (ret < 0) - return ret; - - ret = clk_prepare_enable(pcdev->clk_csi_per); - if (ret < 0) - goto exit_csi_ahb; - - csicr1 = CSICR1_MCLKEN | CSICR1_PRP_IF_EN | CSICR1_FCC | - CSICR1_RXFF_LEVEL(0); - - pcdev->csicr1 = csicr1; - writel(pcdev->csicr1, pcdev->base_csi + CSICR1); - - pcdev->frame_count = 0; - - return 0; - -exit_csi_ahb: - clk_disable_unprepare(pcdev->clk_csi_ahb); - - return ret; -} - -static void mx2_camera_clock_stop(struct soc_camera_host *ici) -{ - struct mx2_camera_dev *pcdev = ici->priv; - - mx2_camera_deactivate(pcdev); -} - -/* - * Videobuf operations - */ -static int mx2_videobuf_setup(struct vb2_queue *vq, - unsigned int *count, unsigned int *num_planes, - unsigned int sizes[], void *alloc_ctxs[]) -{ - struct soc_camera_device *icd = soc_camera_from_vb2q(vq); - struct soc_camera_host *ici = to_soc_camera_host(icd->parent); - struct mx2_camera_dev *pcdev = ici->priv; - - dev_dbg(icd->parent, "count=%d, size=%d\n", *count, sizes[0]); - - alloc_ctxs[0] = pcdev->alloc_ctx; - - sizes[0] = icd->sizeimage; - - if (0 == *count) - *count = 32; - if (!*num_planes && - sizes[0] * *count > MAX_VIDEO_MEM * 1024 * 1024) - *count = (MAX_VIDEO_MEM * 1024 * 1024) / sizes[0]; - - *num_planes = 1; - - return 0; -} - -static int mx2_videobuf_prepare(struct vb2_buffer *vb) -{ - struct soc_camera_device *icd = soc_camera_from_vb2q(vb->vb2_queue); - int ret = 0; - - dev_dbg(icd->parent, "%s (vb=0x%p) 0x%p %lu\n", __func__, - vb, vb2_plane_vaddr(vb, 0), vb2_get_plane_payload(vb, 0)); - -#ifdef DEBUG - /* - * This can be useful if you want to see if we actually fill - * the buffer with something - */ - memset((void *)vb2_plane_vaddr(vb, 0), - 0xaa, vb2_get_plane_payload(vb, 0)); -#endif - - vb2_set_plane_payload(vb, 0, icd->sizeimage); - if (vb2_plane_vaddr(vb, 0) && - vb2_get_plane_payload(vb, 0) > vb2_plane_size(vb, 0)) { - ret = -EINVAL; - goto out; - } - - return 0; - -out: - return ret; -} - -static void mx2_videobuf_queue(struct vb2_buffer *vb) -{ - struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); - struct soc_camera_device *icd = soc_camera_from_vb2q(vb->vb2_queue); - struct soc_camera_host *ici = - to_soc_camera_host(icd->parent); - struct mx2_camera_dev *pcdev = ici->priv; - struct mx2_buffer *buf = container_of(vbuf, struct mx2_buffer, vb); - unsigned long flags; - - dev_dbg(icd->parent, "%s (vb=0x%p) 0x%p %lu\n", __func__, - vb, vb2_plane_vaddr(vb, 0), vb2_get_plane_payload(vb, 0)); - - spin_lock_irqsave(&pcdev->lock, flags); - - list_add_tail(&buf->internal.queue, &pcdev->capture); - - spin_unlock_irqrestore(&pcdev->lock, flags); -} - -static void mx27_camera_emma_buf_init(struct soc_camera_device *icd, - int bytesperline) -{ - struct soc_camera_host *ici = - to_soc_camera_host(icd->parent); - struct mx2_camera_dev *pcdev = ici->priv; - struct mx2_fmt_cfg *prp = pcdev->emma_prp; - - writel((pcdev->s_width << 16) | pcdev->s_height, - pcdev->base_emma + PRP_SRC_FRAME_SIZE); - writel(prp->cfg.src_pixel, - pcdev->base_emma + PRP_SRC_PIXEL_FORMAT_CNTL); - if (prp->cfg.channel == 1) { - writel((icd->user_width << 16) | icd->user_height, - pcdev->base_emma + PRP_CH1_OUT_IMAGE_SIZE); - writel(bytesperline, - pcdev->base_emma + PRP_DEST_CH1_LINE_STRIDE); - writel(prp->cfg.ch1_pixel, - pcdev->base_emma + PRP_CH1_PIXEL_FORMAT_CNTL); - } else { /* channel 2 */ - writel((icd->user_width << 16) | icd->user_height, - pcdev->base_emma + PRP_CH2_OUT_IMAGE_SIZE); - } - - /* Enable interrupts */ - writel(prp->cfg.irq_flags, pcdev->base_emma + PRP_INTR_CNTL); -} - -static void mx2_prp_resize_commit(struct mx2_camera_dev *pcdev) -{ - int dir; - - for (dir = RESIZE_DIR_H; dir <= RESIZE_DIR_V; dir++) { - unsigned char *s = pcdev->resizing[dir].s; - int len = pcdev->resizing[dir].len; - unsigned int coeff[2] = {0, 0}; - unsigned int valid = 0; - int i; - - if (len == 0) - continue; - - for (i = RESIZE_NUM_MAX - 1; i >= 0; i--) { - int j; - - j = i > 9 ? 1 : 0; - coeff[j] = (coeff[j] << BC_COEF) | - (s[i] & (SZ_COEF - 1)); - - if (i == 5 || i == 15) - coeff[j] <<= 1; - - valid = (valid << 1) | (s[i] >> BC_COEF); - } - - valid |= PRP_RZ_VALID_TBL_LEN(len); - - if (pcdev->resizing[dir].algo == RESIZE_ALGO_BILINEAR) - valid |= PRP_RZ_VALID_BILINEAR; - - if (pcdev->emma_prp->cfg.channel == 1) { - if (dir == RESIZE_DIR_H) { - writel(coeff[0], pcdev->base_emma + - PRP_CH1_RZ_HORI_COEF1); - writel(coeff[1], pcdev->base_emma + - PRP_CH1_RZ_HORI_COEF2); - writel(valid, pcdev->base_emma + - PRP_CH1_RZ_HORI_VALID); - } else { - writel(coeff[0], pcdev->base_emma + - PRP_CH1_RZ_VERT_COEF1); - writel(coeff[1], pcdev->base_emma + - PRP_CH1_RZ_VERT_COEF2); - writel(valid, pcdev->base_emma + - PRP_CH1_RZ_VERT_VALID); - } - } else { - if (dir == RESIZE_DIR_H) { - writel(coeff[0], pcdev->base_emma + - PRP_CH2_RZ_HORI_COEF1); - writel(coeff[1], pcdev->base_emma + - PRP_CH2_RZ_HORI_COEF2); - writel(valid, pcdev->base_emma + - PRP_CH2_RZ_HORI_VALID); - } else { - writel(coeff[0], pcdev->base_emma + - PRP_CH2_RZ_VERT_COEF1); - writel(coeff[1], pcdev->base_emma + - PRP_CH2_RZ_VERT_COEF2); - writel(valid, pcdev->base_emma + - PRP_CH2_RZ_VERT_VALID); - } - } - } -} - -static int mx2_start_streaming(struct vb2_queue *q, unsigned int count) -{ - struct soc_camera_device *icd = soc_camera_from_vb2q(q); - struct soc_camera_host *ici = - to_soc_camera_host(icd->parent); - struct mx2_camera_dev *pcdev = ici->priv; - struct mx2_fmt_cfg *prp = pcdev->emma_prp; - struct vb2_buffer *vb; - struct mx2_buffer *buf; - unsigned long phys; - int bytesperline; - unsigned long flags; - - if (count < 2) - return -ENOBUFS; - - spin_lock_irqsave(&pcdev->lock, flags); - - buf = list_first_entry(&pcdev->capture, struct mx2_buffer, - internal.queue); - buf->internal.bufnum = 0; - vb = &buf->vb.vb2_buf; - - phys = vb2_dma_contig_plane_dma_addr(vb, 0); - mx27_update_emma_buf(pcdev, phys, buf->internal.bufnum); - list_move_tail(pcdev->capture.next, &pcdev->active_bufs); - - buf = list_first_entry(&pcdev->capture, struct mx2_buffer, - internal.queue); - buf->internal.bufnum = 1; - vb = &buf->vb.vb2_buf; - - phys = vb2_dma_contig_plane_dma_addr(vb, 0); - mx27_update_emma_buf(pcdev, phys, buf->internal.bufnum); - list_move_tail(pcdev->capture.next, &pcdev->active_bufs); - - bytesperline = soc_mbus_bytes_per_line(icd->user_width, - icd->current_fmt->host_fmt); - if (bytesperline < 0) { - spin_unlock_irqrestore(&pcdev->lock, flags); - return bytesperline; - } - - /* - * I didn't manage to properly enable/disable the prp - * on a per frame basis during running transfers, - * thus we allocate a buffer here and use it to - * discard frames when no buffer is available. - * Feel free to work on this ;) - */ - pcdev->discard_size = icd->user_height * bytesperline; - pcdev->discard_buffer = dma_alloc_coherent(ici->v4l2_dev.dev, - pcdev->discard_size, - &pcdev->discard_buffer_dma, GFP_ATOMIC); - if (!pcdev->discard_buffer) { - spin_unlock_irqrestore(&pcdev->lock, flags); - return -ENOMEM; - } - - pcdev->buf_discard[0].discard = true; - list_add_tail(&pcdev->buf_discard[0].queue, - &pcdev->discard); - - pcdev->buf_discard[1].discard = true; - list_add_tail(&pcdev->buf_discard[1].queue, - &pcdev->discard); - - mx2_prp_resize_commit(pcdev); - - mx27_camera_emma_buf_init(icd, bytesperline); - - if (prp->cfg.channel == 1) { - writel(PRP_CNTL_CH1EN | - PRP_CNTL_CSIEN | - prp->cfg.in_fmt | - prp->cfg.out_fmt | - PRP_CNTL_CH1_LEN | - PRP_CNTL_CH1BYP | - PRP_CNTL_CH1_TSKIP(0) | - PRP_CNTL_IN_TSKIP(0), - pcdev->base_emma + PRP_CNTL); - } else { - writel(PRP_CNTL_CH2EN | - PRP_CNTL_CSIEN | - prp->cfg.in_fmt | - prp->cfg.out_fmt | - PRP_CNTL_CH2_LEN | - PRP_CNTL_CH2_TSKIP(0) | - PRP_CNTL_IN_TSKIP(0), - pcdev->base_emma + PRP_CNTL); - } - spin_unlock_irqrestore(&pcdev->lock, flags); - - return 0; -} - -static void mx2_stop_streaming(struct vb2_queue *q) -{ - struct soc_camera_device *icd = soc_camera_from_vb2q(q); - struct soc_camera_host *ici = - to_soc_camera_host(icd->parent); - struct mx2_camera_dev *pcdev = ici->priv; - struct mx2_fmt_cfg *prp = pcdev->emma_prp; - unsigned long flags; - void *b; - u32 cntl; - - spin_lock_irqsave(&pcdev->lock, flags); - - cntl = readl(pcdev->base_emma + PRP_CNTL); - if (prp->cfg.channel == 1) { - writel(cntl & ~PRP_CNTL_CH1EN, - pcdev->base_emma + PRP_CNTL); - } else { - writel(cntl & ~PRP_CNTL_CH2EN, - pcdev->base_emma + PRP_CNTL); - } - INIT_LIST_HEAD(&pcdev->capture); - INIT_LIST_HEAD(&pcdev->active_bufs); - INIT_LIST_HEAD(&pcdev->discard); - - b = pcdev->discard_buffer; - pcdev->discard_buffer = NULL; - - spin_unlock_irqrestore(&pcdev->lock, flags); - - dma_free_coherent(ici->v4l2_dev.dev, - pcdev->discard_size, b, pcdev->discard_buffer_dma); -} - -static struct vb2_ops mx2_videobuf_ops = { - .queue_setup = mx2_videobuf_setup, - .buf_prepare = mx2_videobuf_prepare, - .buf_queue = mx2_videobuf_queue, - .start_streaming = mx2_start_streaming, - .stop_streaming = mx2_stop_streaming, -}; - -static int mx2_camera_init_videobuf(struct vb2_queue *q, - struct soc_camera_device *icd) -{ - q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - q->io_modes = VB2_MMAP | VB2_USERPTR; - q->drv_priv = icd; - q->ops = &mx2_videobuf_ops; - q->mem_ops = &vb2_dma_contig_memops; - q->buf_struct_size = sizeof(struct mx2_buffer); - q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; - - return vb2_queue_init(q); -} - -#define MX2_BUS_FLAGS (V4L2_MBUS_MASTER | \ - V4L2_MBUS_VSYNC_ACTIVE_HIGH | \ - V4L2_MBUS_VSYNC_ACTIVE_LOW | \ - V4L2_MBUS_HSYNC_ACTIVE_HIGH | \ - V4L2_MBUS_HSYNC_ACTIVE_LOW | \ - V4L2_MBUS_PCLK_SAMPLE_RISING | \ - V4L2_MBUS_PCLK_SAMPLE_FALLING | \ - V4L2_MBUS_DATA_ACTIVE_HIGH | \ - V4L2_MBUS_DATA_ACTIVE_LOW) - -static int mx27_camera_emma_prp_reset(struct mx2_camera_dev *pcdev) -{ - int count = 0; - - readl(pcdev->base_emma + PRP_CNTL); - writel(PRP_CNTL_SWRST, pcdev->base_emma + PRP_CNTL); - while (count++ < 100) { - if (!(readl(pcdev->base_emma + PRP_CNTL) & PRP_CNTL_SWRST)) - return 0; - barrier(); - udelay(1); - } - - return -ETIMEDOUT; -} - -static int mx2_camera_set_bus_param(struct soc_camera_device *icd) -{ - struct v4l2_subdev *sd = soc_camera_to_subdev(icd); - struct soc_camera_host *ici = to_soc_camera_host(icd->parent); - struct mx2_camera_dev *pcdev = ici->priv; - struct v4l2_mbus_config cfg = {.type = V4L2_MBUS_PARALLEL,}; - unsigned long common_flags; - int ret; - int bytesperline; - u32 csicr1 = pcdev->csicr1; - - ret = v4l2_subdev_call(sd, video, g_mbus_config, &cfg); - if (!ret) { - common_flags = soc_mbus_config_compatible(&cfg, MX2_BUS_FLAGS); - if (!common_flags) { - dev_warn(icd->parent, - "Flags incompatible: camera 0x%x, host 0x%x\n", - cfg.flags, MX2_BUS_FLAGS); - return -EINVAL; - } - } else if (ret != -ENOIOCTLCMD) { - return ret; - } else { - common_flags = MX2_BUS_FLAGS; - } - - if ((common_flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) && - (common_flags & V4L2_MBUS_HSYNC_ACTIVE_LOW)) { - if (pcdev->platform_flags & MX2_CAMERA_HSYNC_HIGH) - common_flags &= ~V4L2_MBUS_HSYNC_ACTIVE_LOW; - else - common_flags &= ~V4L2_MBUS_HSYNC_ACTIVE_HIGH; - } - - if ((common_flags & V4L2_MBUS_PCLK_SAMPLE_RISING) && - (common_flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)) { - if (pcdev->platform_flags & MX2_CAMERA_PCLK_SAMPLE_RISING) - common_flags &= ~V4L2_MBUS_PCLK_SAMPLE_FALLING; - else - common_flags &= ~V4L2_MBUS_PCLK_SAMPLE_RISING; - } - - cfg.flags = common_flags; - ret = v4l2_subdev_call(sd, video, s_mbus_config, &cfg); - if (ret < 0 && ret != -ENOIOCTLCMD) { - dev_dbg(icd->parent, "camera s_mbus_config(0x%lx) returned %d\n", - common_flags, ret); - return ret; - } - - csicr1 = (csicr1 & ~CSICR1_FMT_MASK) | pcdev->emma_prp->cfg.csicr1; - - if (common_flags & V4L2_MBUS_PCLK_SAMPLE_RISING) - csicr1 |= CSICR1_REDGE; - if (common_flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) - csicr1 |= CSICR1_SOF_POL; - if (common_flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) - csicr1 |= CSICR1_HSYNC_POL; - if (pcdev->platform_flags & MX2_CAMERA_EXT_VSYNC) - csicr1 |= CSICR1_EXT_VSYNC; - if (pcdev->platform_flags & MX2_CAMERA_CCIR) - csicr1 |= CSICR1_CCIR_EN; - if (pcdev->platform_flags & MX2_CAMERA_CCIR_INTERLACE) - csicr1 |= CSICR1_CCIR_MODE; - if (pcdev->platform_flags & MX2_CAMERA_GATED_CLOCK) - csicr1 |= CSICR1_GCLK_MODE; - if (pcdev->platform_flags & MX2_CAMERA_INV_DATA) - csicr1 |= CSICR1_INV_DATA; - - pcdev->csicr1 = csicr1; - - bytesperline = soc_mbus_bytes_per_line(icd->user_width, - icd->current_fmt->host_fmt); - if (bytesperline < 0) - return bytesperline; - - ret = mx27_camera_emma_prp_reset(pcdev); - if (ret) - return ret; - - writel(pcdev->csicr1, pcdev->base_csi + CSICR1); - - return 0; -} - -static int mx2_camera_set_crop(struct soc_camera_device *icd, - const struct v4l2_crop *a) -{ - struct v4l2_crop a_writable = *a; - struct v4l2_rect *rect = &a_writable.c; - struct v4l2_subdev *sd = soc_camera_to_subdev(icd); - struct v4l2_subdev_format fmt = { - .which = V4L2_SUBDEV_FORMAT_ACTIVE, - }; - struct v4l2_mbus_framefmt *mf = &fmt.format; - int ret; - - soc_camera_limit_side(&rect->left, &rect->width, 0, 2, 4096); - soc_camera_limit_side(&rect->top, &rect->height, 0, 2, 4096); - - ret = v4l2_subdev_call(sd, video, s_crop, a); - if (ret < 0) - return ret; - - /* The capture device might have changed its output */ - ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &fmt); - if (ret < 0) - return ret; - - dev_dbg(icd->parent, "Sensor cropped %dx%d\n", - mf->width, mf->height); - - icd->user_width = mf->width; - icd->user_height = mf->height; - - return ret; -} - -static int mx2_camera_get_formats(struct soc_camera_device *icd, - unsigned int idx, - struct soc_camera_format_xlate *xlate) -{ - struct v4l2_subdev *sd = soc_camera_to_subdev(icd); - const struct soc_mbus_pixelfmt *fmt; - struct device *dev = icd->parent; - struct v4l2_subdev_mbus_code_enum code = { - .which = V4L2_SUBDEV_FORMAT_ACTIVE, - .index = idx, - }; - int ret, formats = 0; - - ret = v4l2_subdev_call(sd, pad, enum_mbus_code, NULL, &code); - if (ret < 0) - /* no more formats */ - return 0; - - fmt = soc_mbus_get_fmtdesc(code.code); - if (!fmt) { - dev_err(dev, "Invalid format code #%u: %d\n", idx, code.code); - return 0; - } - - if (code.code == MEDIA_BUS_FMT_YUYV8_2X8 || - code.code == MEDIA_BUS_FMT_UYVY8_2X8) { - formats++; - if (xlate) { - /* - * CH2 can output YUV420 which is a standard format in - * soc_mediabus.c - */ - xlate->host_fmt = - soc_mbus_get_fmtdesc(MEDIA_BUS_FMT_YUYV8_1_5X8); - xlate->code = code.code; - dev_dbg(dev, "Providing host format %s for sensor code %d\n", - xlate->host_fmt->name, code.code); - xlate++; - } - } - - if (code.code == MEDIA_BUS_FMT_UYVY8_2X8) { - formats++; - if (xlate) { - xlate->host_fmt = - soc_mbus_get_fmtdesc(MEDIA_BUS_FMT_YUYV8_2X8); - xlate->code = code.code; - dev_dbg(dev, "Providing host format %s for sensor code %d\n", - xlate->host_fmt->name, code.code); - xlate++; - } - } - - /* Generic pass-trough */ - formats++; - if (xlate) { - xlate->host_fmt = fmt; - xlate->code = code.code; - xlate++; - } - return formats; -} - -static int mx2_emmaprp_resize(struct mx2_camera_dev *pcdev, - struct v4l2_mbus_framefmt *mf_in, - struct v4l2_pix_format *pix_out, bool apply) -{ - unsigned int num, den; - unsigned long m; - int i, dir; - - for (dir = RESIZE_DIR_H; dir <= RESIZE_DIR_V; dir++) { - struct emma_prp_resize tmprsz; - unsigned char *s = tmprsz.s; - int len = 0; - int in, out; - - if (dir == RESIZE_DIR_H) { - in = mf_in->width; - out = pix_out->width; - } else { - in = mf_in->height; - out = pix_out->height; - } - - if (in < out) - return -EINVAL; - else if (in == out) - continue; - - /* Calculate ratio */ - m = gcd(in, out); - num = in / m; - den = out / m; - if (num > RESIZE_NUM_MAX) - return -EINVAL; - - if ((num >= 2 * den) && (den == 1) && - (num < 9) && (!(num & 0x01))) { - int sum = 0; - int j; - - /* Average scaling for >= 2:1 ratios */ - /* Support can be added for num >=9 and odd values */ - - tmprsz.algo = RESIZE_ALGO_AVERAGING; - len = num; - - for (i = 0; i < (len / 2); i++) - s[i] = 8; - - do { - for (i = 0; i < (len / 2); i++) { - s[i] = s[i] >> 1; - sum = 0; - for (j = 0; j < (len / 2); j++) - sum += s[j]; - if (sum == 4) - break; - } - } while (sum != 4); - - for (i = (len / 2); i < len; i++) - s[i] = s[len - i - 1]; - - s[len - 1] |= SZ_COEF; - } else { - /* bilinear scaling for < 2:1 ratios */ - int v; /* overflow counter */ - int coeff, nxt; /* table output */ - int in_pos_inc = 2 * den; - int out_pos = num; - int out_pos_inc = 2 * num; - int init_carry = num - den; - int carry = init_carry; - - tmprsz.algo = RESIZE_ALGO_BILINEAR; - v = den + in_pos_inc; - do { - coeff = v - out_pos; - out_pos += out_pos_inc; - carry += out_pos_inc; - for (nxt = 0; v < out_pos; nxt++) { - v += in_pos_inc; - carry -= in_pos_inc; - } - - if (len > RESIZE_NUM_MAX) - return -EINVAL; - - coeff = ((coeff << BC_COEF) + - (in_pos_inc >> 1)) / in_pos_inc; - - if (coeff >= (SZ_COEF - 1)) - coeff--; - - coeff |= SZ_COEF; - s[len] = (unsigned char)coeff; - len++; - - for (i = 1; i < nxt; i++) { - if (len >= RESIZE_NUM_MAX) - return -EINVAL; - s[len] = 0; - len++; - } - } while (carry != init_carry); - } - tmprsz.len = len; - if (dir == RESIZE_DIR_H) - mf_in->width = pix_out->width; - else - mf_in->height = pix_out->height; - - if (apply) - memcpy(&pcdev->resizing[dir], &tmprsz, sizeof(tmprsz)); - } - return 0; -} - -static int mx2_camera_set_fmt(struct soc_camera_device *icd, - struct v4l2_format *f) -{ - struct soc_camera_host *ici = to_soc_camera_host(icd->parent); - struct mx2_camera_dev *pcdev = ici->priv; - struct v4l2_subdev *sd = soc_camera_to_subdev(icd); - const struct soc_camera_format_xlate *xlate; - struct v4l2_pix_format *pix = &f->fmt.pix; - struct v4l2_subdev_format format = { - .which = V4L2_SUBDEV_FORMAT_ACTIVE, - }; - struct v4l2_mbus_framefmt *mf = &format.format; - int ret; - - dev_dbg(icd->parent, "%s: requested params: width = %d, height = %d\n", - __func__, pix->width, pix->height); - - xlate = soc_camera_xlate_by_fourcc(icd, pix->pixelformat); - if (!xlate) { - dev_warn(icd->parent, "Format %x not found\n", - pix->pixelformat); - return -EINVAL; - } - - mf->width = pix->width; - mf->height = pix->height; - mf->field = pix->field; - mf->colorspace = pix->colorspace; - mf->code = xlate->code; - - ret = v4l2_subdev_call(sd, pad, set_fmt, NULL, &format); - if (ret < 0 && ret != -ENOIOCTLCMD) - return ret; - - /* Store width and height returned by the sensor for resizing */ - pcdev->s_width = mf->width; - pcdev->s_height = mf->height; - dev_dbg(icd->parent, "%s: sensor params: width = %d, height = %d\n", - __func__, pcdev->s_width, pcdev->s_height); - - pcdev->emma_prp = mx27_emma_prp_get_format(xlate->code, - xlate->host_fmt->fourcc); - - memset(pcdev->resizing, 0, sizeof(pcdev->resizing)); - if ((mf->width != pix->width || mf->height != pix->height) && - pcdev->emma_prp->cfg.in_fmt == PRP_CNTL_DATA_IN_YUV422) { - if (mx2_emmaprp_resize(pcdev, mf, pix, true) < 0) - dev_dbg(icd->parent, "%s: can't resize\n", __func__); - } - - if (mf->code != xlate->code) - return -EINVAL; - - pix->width = mf->width; - pix->height = mf->height; - pix->field = mf->field; - pix->colorspace = mf->colorspace; - icd->current_fmt = xlate; - - dev_dbg(icd->parent, "%s: returned params: width = %d, height = %d\n", - __func__, pix->width, pix->height); - - return 0; -} - -static int mx2_camera_try_fmt(struct soc_camera_device *icd, - struct v4l2_format *f) -{ - struct v4l2_subdev *sd = soc_camera_to_subdev(icd); - const struct soc_camera_format_xlate *xlate; - struct v4l2_pix_format *pix = &f->fmt.pix; - struct v4l2_subdev_pad_config pad_cfg; - struct v4l2_subdev_format format = { - .which = V4L2_SUBDEV_FORMAT_TRY, - }; - struct v4l2_mbus_framefmt *mf = &format.format; - __u32 pixfmt = pix->pixelformat; - struct soc_camera_host *ici = to_soc_camera_host(icd->parent); - struct mx2_camera_dev *pcdev = ici->priv; - struct mx2_fmt_cfg *emma_prp; - int ret; - - dev_dbg(icd->parent, "%s: requested params: width = %d, height = %d\n", - __func__, pix->width, pix->height); - - xlate = soc_camera_xlate_by_fourcc(icd, pixfmt); - if (pixfmt && !xlate) { - dev_warn(icd->parent, "Format %x not found\n", pixfmt); - return -EINVAL; - } - - /* - * limit to MX27 hardware capabilities: width must be a multiple of 8 as - * requested by the CSI. (Table 39-2 in the i.MX27 Reference Manual). - */ - pix->width &= ~0x7; - - /* limit to sensor capabilities */ - mf->width = pix->width; - mf->height = pix->height; - mf->field = pix->field; - mf->colorspace = pix->colorspace; - mf->code = xlate->code; - - ret = v4l2_subdev_call(sd, pad, set_fmt, &pad_cfg, &format); - if (ret < 0) - return ret; - - dev_dbg(icd->parent, "%s: sensor params: width = %d, height = %d\n", - __func__, pcdev->s_width, pcdev->s_height); - - /* If the sensor does not support image size try PrP resizing */ - emma_prp = mx27_emma_prp_get_format(xlate->code, - xlate->host_fmt->fourcc); - - if ((mf->width != pix->width || mf->height != pix->height) && - emma_prp->cfg.in_fmt == PRP_CNTL_DATA_IN_YUV422) { - if (mx2_emmaprp_resize(pcdev, mf, pix, false) < 0) - dev_dbg(icd->parent, "%s: can't resize\n", __func__); - } - - if (mf->field == V4L2_FIELD_ANY) - mf->field = V4L2_FIELD_NONE; - /* - * Driver supports interlaced images provided they have - * both fields so that they can be processed as if they - * were progressive. - */ - if (mf->field != V4L2_FIELD_NONE && !V4L2_FIELD_HAS_BOTH(mf->field)) { - dev_err(icd->parent, "Field type %d unsupported.\n", - mf->field); - return -EINVAL; - } - - pix->width = mf->width; - pix->height = mf->height; - pix->field = mf->field; - pix->colorspace = mf->colorspace; - - dev_dbg(icd->parent, "%s: returned params: width = %d, height = %d\n", - __func__, pix->width, pix->height); - - return 0; -} - -static int mx2_camera_querycap(struct soc_camera_host *ici, - struct v4l2_capability *cap) -{ - /* cap->name is set by the friendly caller:-> */ - strlcpy(cap->card, MX2_CAM_DRIVER_DESCRIPTION, sizeof(cap->card)); - cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; - cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; - - return 0; -} - -static unsigned int mx2_camera_poll(struct file *file, poll_table *pt) -{ - struct soc_camera_device *icd = file->private_data; - - return vb2_poll(&icd->vb2_vidq, file, pt); -} - -static struct soc_camera_host_ops mx2_soc_camera_host_ops = { - .owner = THIS_MODULE, - .add = mx2_camera_add_device, - .remove = mx2_camera_remove_device, - .clock_start = mx2_camera_clock_start, - .clock_stop = mx2_camera_clock_stop, - .set_fmt = mx2_camera_set_fmt, - .set_crop = mx2_camera_set_crop, - .get_formats = mx2_camera_get_formats, - .try_fmt = mx2_camera_try_fmt, - .init_videobuf2 = mx2_camera_init_videobuf, - .poll = mx2_camera_poll, - .querycap = mx2_camera_querycap, - .set_bus_param = mx2_camera_set_bus_param, -}; - -static void mx27_camera_frame_done_emma(struct mx2_camera_dev *pcdev, - int bufnum, bool err) -{ -#ifdef DEBUG - struct mx2_fmt_cfg *prp = pcdev->emma_prp; -#endif - struct mx2_buf_internal *ibuf; - struct mx2_buffer *buf; - struct vb2_buffer *vb; - struct vb2_v4l2_buffer *vbuf; - unsigned long phys; - - ibuf = list_first_entry(&pcdev->active_bufs, struct mx2_buf_internal, - queue); - - BUG_ON(ibuf->bufnum != bufnum); - - if (ibuf->discard) { - /* - * Discard buffer must not be returned to user space. - * Just return it to the discard queue. - */ - list_move_tail(pcdev->active_bufs.next, &pcdev->discard); - } else { - buf = mx2_ibuf_to_buf(ibuf); - - vb = &buf->vb.vb2_buf; - vbuf = to_vb2_v4l2_buffer(vb); -#ifdef DEBUG - phys = vb2_dma_contig_plane_dma_addr(vb, 0); - if (prp->cfg.channel == 1) { - if (readl(pcdev->base_emma + PRP_DEST_RGB1_PTR + - 4 * bufnum) != phys) { - dev_err(pcdev->dev, "%lx != %x\n", phys, - readl(pcdev->base_emma + - PRP_DEST_RGB1_PTR + 4 * bufnum)); - } - } else { - if (readl(pcdev->base_emma + PRP_DEST_Y_PTR - - 0x14 * bufnum) != phys) { - dev_err(pcdev->dev, "%lx != %x\n", phys, - readl(pcdev->base_emma + - PRP_DEST_Y_PTR - 0x14 * bufnum)); - } - } -#endif - dev_dbg(pcdev->dev, "%s (vb=0x%p) 0x%p %lu\n", __func__, vb, - vb2_plane_vaddr(vb, 0), - vb2_get_plane_payload(vb, 0)); - - list_del_init(&buf->internal.queue); - vb->timestamp = ktime_get_ns(); - vbuf->sequence = pcdev->frame_count; - if (err) - vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); - else - vb2_buffer_done(vb, VB2_BUF_STATE_DONE); - } - - pcdev->frame_count++; - - if (list_empty(&pcdev->capture)) { - if (list_empty(&pcdev->discard)) { - dev_warn(pcdev->dev, "%s: trying to access empty discard list\n", - __func__); - return; - } - - ibuf = list_first_entry(&pcdev->discard, - struct mx2_buf_internal, queue); - ibuf->bufnum = bufnum; - - list_move_tail(pcdev->discard.next, &pcdev->active_bufs); - mx27_update_emma_buf(pcdev, pcdev->discard_buffer_dma, bufnum); - return; - } - - buf = list_first_entry(&pcdev->capture, struct mx2_buffer, - internal.queue); - - buf->internal.bufnum = bufnum; - - list_move_tail(pcdev->capture.next, &pcdev->active_bufs); - - vb = &buf->vb.vb2_buf; - - phys = vb2_dma_contig_plane_dma_addr(vb, 0); - mx27_update_emma_buf(pcdev, phys, bufnum); -} - -static irqreturn_t mx27_camera_emma_irq(int irq_emma, void *data) -{ - struct mx2_camera_dev *pcdev = data; - unsigned int status = readl(pcdev->base_emma + PRP_INTRSTATUS); - struct mx2_buf_internal *ibuf; - - spin_lock(&pcdev->lock); - - if (list_empty(&pcdev->active_bufs)) { - dev_warn(pcdev->dev, "%s: called while active list is empty\n", - __func__); - - if (!status) { - spin_unlock(&pcdev->lock); - return IRQ_NONE; - } - } - - if (status & (1 << 7)) { /* overflow */ - u32 cntl = readl(pcdev->base_emma + PRP_CNTL); - writel(cntl & ~(PRP_CNTL_CH1EN | PRP_CNTL_CH2EN), - pcdev->base_emma + PRP_CNTL); - writel(cntl, pcdev->base_emma + PRP_CNTL); - - ibuf = list_first_entry(&pcdev->active_bufs, - struct mx2_buf_internal, queue); - mx27_camera_frame_done_emma(pcdev, - ibuf->bufnum, true); - - status &= ~(1 << 7); - } else if (((status & (3 << 5)) == (3 << 5)) || - ((status & (3 << 3)) == (3 << 3))) { - /* - * Both buffers have triggered, process the one we're expecting - * to first - */ - ibuf = list_first_entry(&pcdev->active_bufs, - struct mx2_buf_internal, queue); - mx27_camera_frame_done_emma(pcdev, ibuf->bufnum, false); - status &= ~(1 << (6 - ibuf->bufnum)); /* mark processed */ - } else if ((status & (1 << 6)) || (status & (1 << 4))) { - mx27_camera_frame_done_emma(pcdev, 0, false); - } else if ((status & (1 << 5)) || (status & (1 << 3))) { - mx27_camera_frame_done_emma(pcdev, 1, false); - } - - spin_unlock(&pcdev->lock); - writel(status, pcdev->base_emma + PRP_INTRSTATUS); - - return IRQ_HANDLED; -} - -static int mx27_camera_emma_init(struct platform_device *pdev) -{ - struct mx2_camera_dev *pcdev = platform_get_drvdata(pdev); - struct resource *res_emma; - int irq_emma; - int err = 0; - - res_emma = platform_get_resource(pdev, IORESOURCE_MEM, 1); - irq_emma = platform_get_irq(pdev, 1); - if (!res_emma || !irq_emma) { - dev_err(pcdev->dev, "no EMMA resources\n"); - err = -ENODEV; - goto out; - } - - pcdev->base_emma = devm_ioremap_resource(pcdev->dev, res_emma); - if (IS_ERR(pcdev->base_emma)) { - err = PTR_ERR(pcdev->base_emma); - goto out; - } - - err = devm_request_irq(pcdev->dev, irq_emma, mx27_camera_emma_irq, 0, - MX2_CAM_DRV_NAME, pcdev); - if (err) { - dev_err(pcdev->dev, "Camera EMMA interrupt register failed\n"); - goto out; - } - - pcdev->clk_emma_ipg = devm_clk_get(pcdev->dev, "emma-ipg"); - if (IS_ERR(pcdev->clk_emma_ipg)) { - err = PTR_ERR(pcdev->clk_emma_ipg); - goto out; - } - - clk_prepare_enable(pcdev->clk_emma_ipg); - - pcdev->clk_emma_ahb = devm_clk_get(pcdev->dev, "emma-ahb"); - if (IS_ERR(pcdev->clk_emma_ahb)) { - err = PTR_ERR(pcdev->clk_emma_ahb); - goto exit_clk_emma_ipg; - } - - clk_prepare_enable(pcdev->clk_emma_ahb); - - err = mx27_camera_emma_prp_reset(pcdev); - if (err) - goto exit_clk_emma_ahb; - - return err; - -exit_clk_emma_ahb: - clk_disable_unprepare(pcdev->clk_emma_ahb); -exit_clk_emma_ipg: - clk_disable_unprepare(pcdev->clk_emma_ipg); -out: - return err; -} - -static int mx2_camera_probe(struct platform_device *pdev) -{ - struct mx2_camera_dev *pcdev; - struct resource *res_csi; - int irq_csi; - int err = 0; - - dev_dbg(&pdev->dev, "initialising\n"); - - res_csi = platform_get_resource(pdev, IORESOURCE_MEM, 0); - irq_csi = platform_get_irq(pdev, 0); - if (res_csi == NULL || irq_csi < 0) { - dev_err(&pdev->dev, "Missing platform resources data\n"); - err = -ENODEV; - goto exit; - } - - pcdev = devm_kzalloc(&pdev->dev, sizeof(*pcdev), GFP_KERNEL); - if (!pcdev) { - dev_err(&pdev->dev, "Could not allocate pcdev\n"); - err = -ENOMEM; - goto exit; - } - - pcdev->clk_csi_ahb = devm_clk_get(&pdev->dev, "ahb"); - if (IS_ERR(pcdev->clk_csi_ahb)) { - dev_err(&pdev->dev, "Could not get csi ahb clock\n"); - err = PTR_ERR(pcdev->clk_csi_ahb); - goto exit; - } - - pcdev->clk_csi_per = devm_clk_get(&pdev->dev, "per"); - if (IS_ERR(pcdev->clk_csi_per)) { - dev_err(&pdev->dev, "Could not get csi per clock\n"); - err = PTR_ERR(pcdev->clk_csi_per); - goto exit; - } - - pcdev->pdata = pdev->dev.platform_data; - if (pcdev->pdata) { - long rate; - - pcdev->platform_flags = pcdev->pdata->flags; - - rate = clk_round_rate(pcdev->clk_csi_per, - pcdev->pdata->clk * 2); - if (rate <= 0) { - err = -ENODEV; - goto exit; - } - err = clk_set_rate(pcdev->clk_csi_per, rate); - if (err < 0) - goto exit; - } - - INIT_LIST_HEAD(&pcdev->capture); - INIT_LIST_HEAD(&pcdev->active_bufs); - INIT_LIST_HEAD(&pcdev->discard); - spin_lock_init(&pcdev->lock); - - pcdev->base_csi = devm_ioremap_resource(&pdev->dev, res_csi); - if (IS_ERR(pcdev->base_csi)) { - err = PTR_ERR(pcdev->base_csi); - goto exit; - } - - pcdev->dev = &pdev->dev; - platform_set_drvdata(pdev, pcdev); - - err = mx27_camera_emma_init(pdev); - if (err) - goto exit; - - /* - * We're done with drvdata here. Clear the pointer so that - * v4l2 core can start using drvdata on its purpose. - */ - platform_set_drvdata(pdev, NULL); - - pcdev->soc_host.drv_name = MX2_CAM_DRV_NAME, - pcdev->soc_host.ops = &mx2_soc_camera_host_ops, - pcdev->soc_host.priv = pcdev; - pcdev->soc_host.v4l2_dev.dev = &pdev->dev; - pcdev->soc_host.nr = pdev->id; - - pcdev->alloc_ctx = vb2_dma_contig_init_ctx(&pdev->dev); - if (IS_ERR(pcdev->alloc_ctx)) { - err = PTR_ERR(pcdev->alloc_ctx); - goto eallocctx; - } - err = soc_camera_host_register(&pcdev->soc_host); - if (err) - goto exit_free_emma; - - dev_info(&pdev->dev, "MX2 Camera (CSI) driver probed, clock frequency: %ld\n", - clk_get_rate(pcdev->clk_csi_per)); - - return 0; - -exit_free_emma: - vb2_dma_contig_cleanup_ctx(pcdev->alloc_ctx); -eallocctx: - clk_disable_unprepare(pcdev->clk_emma_ipg); - clk_disable_unprepare(pcdev->clk_emma_ahb); -exit: - return err; -} - -static int mx2_camera_remove(struct platform_device *pdev) -{ - struct soc_camera_host *soc_host = to_soc_camera_host(&pdev->dev); - struct mx2_camera_dev *pcdev = container_of(soc_host, - struct mx2_camera_dev, soc_host); - - soc_camera_host_unregister(&pcdev->soc_host); - - vb2_dma_contig_cleanup_ctx(pcdev->alloc_ctx); - - clk_disable_unprepare(pcdev->clk_emma_ipg); - clk_disable_unprepare(pcdev->clk_emma_ahb); - - dev_info(&pdev->dev, "MX2 Camera driver unloaded\n"); - - return 0; -} - -static struct platform_driver mx2_camera_driver = { - .driver = { - .name = MX2_CAM_DRV_NAME, - }, - .id_table = mx2_camera_devtype, - .remove = mx2_camera_remove, -}; - -module_platform_driver_probe(mx2_camera_driver, mx2_camera_probe); - -MODULE_DESCRIPTION("i.MX27 SoC Camera Host driver"); -MODULE_AUTHOR("Sascha Hauer <sha@pengutronix.de>"); -MODULE_LICENSE("GPL"); -MODULE_VERSION(MX2_CAM_VERSION); diff --git a/drivers/staging/media/mx3/Kconfig b/drivers/staging/media/mx3/Kconfig deleted file mode 100644 index 595d5fe7cad1..000000000000 --- a/drivers/staging/media/mx3/Kconfig +++ /dev/null @@ -1,15 +0,0 @@ -config VIDEO_MX3 - tristate "i.MX3x Camera Sensor Interface driver" - depends on VIDEO_DEV && MX3_IPU && SOC_CAMERA - depends on MX3_IPU || COMPILE_TEST - depends on HAS_DMA - select VIDEOBUF2_DMA_CONTIG - ---help--- - This is a v4l2 driver for the i.MX3x Camera Sensor Interface - - This driver is deprecated: it should become a stand-alone driver - instead of using the soc-camera framework. - - Unless someone is willing to take this on (unlikely with such - ancient hardware) it is going to be removed from the kernel - soon. diff --git a/drivers/staging/media/mx3/Makefile b/drivers/staging/media/mx3/Makefile deleted file mode 100644 index 6d91dcd80c1d..000000000000 --- a/drivers/staging/media/mx3/Makefile +++ /dev/null @@ -1,3 +0,0 @@ -# Makefile for i.MX3x Camera Sensor driver - -obj-$(CONFIG_VIDEO_MX3) += mx3_camera.o diff --git a/drivers/staging/media/mx3/TODO b/drivers/staging/media/mx3/TODO deleted file mode 100644 index bc68fa443a3e..000000000000 --- a/drivers/staging/media/mx3/TODO +++ /dev/null @@ -1,10 +0,0 @@ -This driver is deprecated: it should become a stand-alone driver instead of -using the soc-camera framework. - -Unless someone is willing to take this on (unlikely with such ancient -hardware) it is going to be removed from the kernel soon. - -Note that trivial patches will not be accepted anymore, only a full conversion. - -If you want to convert this driver, please contact the linux-media mailinglist -(see http://linuxtv.org/lists.php). diff --git a/drivers/staging/media/mx3/mx3_camera.c b/drivers/staging/media/mx3/mx3_camera.c deleted file mode 100644 index aa39e9569b1a..000000000000 --- a/drivers/staging/media/mx3/mx3_camera.c +++ /dev/null @@ -1,1264 +0,0 @@ -/* - * V4L2 Driver for i.MX3x camera host - * - * Copyright (C) 2008 - * Guennadi Liakhovetski, DENX Software Engineering, <lg@denx.de> - * - * 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. - */ - -#include <linux/init.h> -#include <linux/module.h> -#include <linux/videodev2.h> -#include <linux/platform_device.h> -#include <linux/clk.h> -#include <linux/vmalloc.h> -#include <linux/interrupt.h> -#include <linux/sched.h> -#include <linux/dma/ipu-dma.h> - -#include <media/v4l2-common.h> -#include <media/v4l2-dev.h> -#include <media/videobuf2-dma-contig.h> -#include <media/soc_camera.h> -#include <media/drv-intf/soc_mediabus.h> - -#include <linux/platform_data/media/camera-mx3.h> -#include <linux/platform_data/dma-imx.h> - -#define MX3_CAM_DRV_NAME "mx3-camera" - -/* CMOS Sensor Interface Registers */ -#define CSI_REG_START 0x60 - -#define CSI_SENS_CONF (0x60 - CSI_REG_START) -#define CSI_SENS_FRM_SIZE (0x64 - CSI_REG_START) -#define CSI_ACT_FRM_SIZE (0x68 - CSI_REG_START) -#define CSI_OUT_FRM_CTRL (0x6C - CSI_REG_START) -#define CSI_TST_CTRL (0x70 - CSI_REG_START) -#define CSI_CCIR_CODE_1 (0x74 - CSI_REG_START) -#define CSI_CCIR_CODE_2 (0x78 - CSI_REG_START) -#define CSI_CCIR_CODE_3 (0x7C - CSI_REG_START) -#define CSI_FLASH_STROBE_1 (0x80 - CSI_REG_START) -#define CSI_FLASH_STROBE_2 (0x84 - CSI_REG_START) - -#define CSI_SENS_CONF_VSYNC_POL_SHIFT 0 -#define CSI_SENS_CONF_HSYNC_POL_SHIFT 1 -#define CSI_SENS_CONF_DATA_POL_SHIFT 2 -#define CSI_SENS_CONF_PIX_CLK_POL_SHIFT 3 -#define CSI_SENS_CONF_SENS_PRTCL_SHIFT 4 -#define CSI_SENS_CONF_SENS_CLKSRC_SHIFT 7 -#define CSI_SENS_CONF_DATA_FMT_SHIFT 8 -#define CSI_SENS_CONF_DATA_WIDTH_SHIFT 10 -#define CSI_SENS_CONF_EXT_VSYNC_SHIFT 15 -#define CSI_SENS_CONF_DIVRATIO_SHIFT 16 - -#define CSI_SENS_CONF_DATA_FMT_RGB_YUV444 (0UL << CSI_SENS_CONF_DATA_FMT_SHIFT) -#define CSI_SENS_CONF_DATA_FMT_YUV422 (2UL << CSI_SENS_CONF_DATA_FMT_SHIFT) -#define CSI_SENS_CONF_DATA_FMT_BAYER (3UL << CSI_SENS_CONF_DATA_FMT_SHIFT) - -#define MAX_VIDEO_MEM 16 - -struct mx3_camera_buffer { - /* common v4l buffer stuff -- must be first */ - struct vb2_v4l2_buffer vb; - struct list_head queue; - - /* One descriptot per scatterlist (per frame) */ - struct dma_async_tx_descriptor *txd; - - /* We have to "build" a scatterlist ourselves - one element per frame */ - struct scatterlist sg; -}; - -/** - * struct mx3_camera_dev - i.MX3x camera (CSI) object - * @dev: camera device, to which the coherent buffer is attached - * @icd: currently attached camera sensor - * @clk: pointer to clock - * @base: remapped register base address - * @pdata: platform data - * @platform_flags: platform flags - * @mclk: master clock frequency in Hz - * @capture: list of capture videobuffers - * @lock: protects video buffer lists - * @active: active video buffer - * @idmac_channel: array of pointers to IPU DMAC DMA channels - * @soc_host: embedded soc_host object - */ -struct mx3_camera_dev { - /* - * i.MX3x is only supposed to handle one camera on its Camera Sensor - * Interface. If anyone ever builds hardware to enable more than one - * camera _simultaneously_, they will have to modify this driver too - */ - struct clk *clk; - - void __iomem *base; - - struct mx3_camera_pdata *pdata; - - unsigned long platform_flags; - unsigned long mclk; - u16 width_flags; /* max 15 bits */ - - struct list_head capture; - spinlock_t lock; /* Protects video buffer lists */ - struct mx3_camera_buffer *active; - size_t buf_total; - struct vb2_alloc_ctx *alloc_ctx; - enum v4l2_field field; - int sequence; - - /* IDMAC / dmaengine interface */ - struct idmac_channel *idmac_channel[1]; /* We need one channel */ - - struct soc_camera_host soc_host; -}; - -struct dma_chan_request { - struct mx3_camera_dev *mx3_cam; - enum ipu_channel id; -}; - -static u32 csi_reg_read(struct mx3_camera_dev *mx3, off_t reg) -{ - return __raw_readl(mx3->base + reg); -} - -static void csi_reg_write(struct mx3_camera_dev *mx3, u32 value, off_t reg) -{ - __raw_writel(value, mx3->base + reg); -} - -static struct mx3_camera_buffer *to_mx3_vb(struct vb2_v4l2_buffer *vb) -{ - return container_of(vb, struct mx3_camera_buffer, vb); -} - -/* Called from the IPU IDMAC ISR */ -static void mx3_cam_dma_done(void *arg) -{ - struct idmac_tx_desc *desc = to_tx_desc(arg); - struct dma_chan *chan = desc->txd.chan; - struct idmac_channel *ichannel = to_idmac_chan(chan); - struct mx3_camera_dev *mx3_cam = ichannel->client; - - dev_dbg(chan->device->dev, "callback cookie %d, active DMA %pad\n", - desc->txd.cookie, mx3_cam->active ? &sg_dma_address(&mx3_cam->active->sg) : NULL); - - spin_lock(&mx3_cam->lock); - if (mx3_cam->active) { - struct vb2_v4l2_buffer *vb = &mx3_cam->active->vb; - struct mx3_camera_buffer *buf = to_mx3_vb(vb); - - list_del_init(&buf->queue); - vb->vb2_buf.timestamp = ktime_get_ns(); - vb->field = mx3_cam->field; - vb->sequence = mx3_cam->sequence++; - vb2_buffer_done(&vb->vb2_buf, VB2_BUF_STATE_DONE); - } - - if (list_empty(&mx3_cam->capture)) { - mx3_cam->active = NULL; - spin_unlock(&mx3_cam->lock); - - /* - * stop capture - without further buffers IPU_CHA_BUF0_RDY will - * not get updated - */ - return; - } - - mx3_cam->active = list_entry(mx3_cam->capture.next, - struct mx3_camera_buffer, queue); - spin_unlock(&mx3_cam->lock); -} - -/* - * Videobuf operations - */ - -/* - * Calculate the __buffer__ (not data) size and number of buffers. - */ -static int mx3_videobuf_setup(struct vb2_queue *vq, - unsigned int *count, unsigned int *num_planes, - unsigned int sizes[], void *alloc_ctxs[]) -{ - struct soc_camera_device *icd = soc_camera_from_vb2q(vq); - struct soc_camera_host *ici = to_soc_camera_host(icd->parent); - struct mx3_camera_dev *mx3_cam = ici->priv; - - if (!mx3_cam->idmac_channel[0]) - return -EINVAL; - - alloc_ctxs[0] = mx3_cam->alloc_ctx; - - if (!vq->num_buffers) - mx3_cam->sequence = 0; - - if (!*count) - *count = 2; - - /* Called from VIDIOC_REQBUFS or in compatibility mode */ - if (!*num_planes) - sizes[0] = icd->sizeimage; - else if (sizes[0] < icd->sizeimage) - return -EINVAL; - - /* If *num_planes != 0, we have already verified *count. */ - if (sizes[0] * *count + mx3_cam->buf_total > MAX_VIDEO_MEM * 1024 * 1024) - *count = (MAX_VIDEO_MEM * 1024 * 1024 - mx3_cam->buf_total) / - sizes[0]; - - *num_planes = 1; - - return 0; -} - -static enum pixel_fmt fourcc_to_ipu_pix(__u32 fourcc) -{ - /* Add more formats as need arises and test possibilities appear... */ - switch (fourcc) { - case V4L2_PIX_FMT_RGB24: - return IPU_PIX_FMT_RGB24; - case V4L2_PIX_FMT_UYVY: - case V4L2_PIX_FMT_RGB565: - default: - return IPU_PIX_FMT_GENERIC; - } -} - -static void mx3_videobuf_queue(struct vb2_buffer *vb) -{ - struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); - struct soc_camera_device *icd = soc_camera_from_vb2q(vb->vb2_queue); - struct soc_camera_host *ici = to_soc_camera_host(icd->parent); - struct mx3_camera_dev *mx3_cam = ici->priv; - struct mx3_camera_buffer *buf = to_mx3_vb(vbuf); - struct scatterlist *sg = &buf->sg; - struct dma_async_tx_descriptor *txd; - struct idmac_channel *ichan = mx3_cam->idmac_channel[0]; - struct idmac_video_param *video = &ichan->params.video; - const struct soc_mbus_pixelfmt *host_fmt = icd->current_fmt->host_fmt; - dma_cookie_t cookie; - size_t new_size; - - new_size = icd->sizeimage; - - if (vb2_plane_size(vb, 0) < new_size) { - dev_err(icd->parent, "Buffer #%d too small (%lu < %zu)\n", - vbuf->vb2_buf.index, vb2_plane_size(vb, 0), new_size); - goto error; - } - - if (!buf->txd) { - sg_dma_address(sg) = vb2_dma_contig_plane_dma_addr(vb, 0); - sg_dma_len(sg) = new_size; - - txd = dmaengine_prep_slave_sg( - &ichan->dma_chan, sg, 1, DMA_DEV_TO_MEM, - DMA_PREP_INTERRUPT); - if (!txd) - goto error; - - txd->callback_param = txd; - txd->callback = mx3_cam_dma_done; - - buf->txd = txd; - } else { - txd = buf->txd; - } - - vb2_set_plane_payload(vb, 0, new_size); - - /* This is the configuration of one sg-element */ - video->out_pixel_fmt = fourcc_to_ipu_pix(host_fmt->fourcc); - - if (video->out_pixel_fmt == IPU_PIX_FMT_GENERIC) { - /* - * If the IPU DMA channel is configured to transfer generic - * 8-bit data, we have to set up the geometry parameters - * correctly, according to the current pixel format. The DMA - * horizontal parameters in this case are expressed in bytes, - * not in pixels. - */ - video->out_width = icd->bytesperline; - video->out_height = icd->user_height; - video->out_stride = icd->bytesperline; - } else { - /* - * For IPU known formats the pixel unit will be managed - * successfully by the IPU code - */ - video->out_width = icd->user_width; - video->out_height = icd->user_height; - video->out_stride = icd->user_width; - } - -#ifdef DEBUG - /* helps to see what DMA actually has written */ - if (vb2_plane_vaddr(vb, 0)) - memset(vb2_plane_vaddr(vb, 0), 0xaa, vb2_get_plane_payload(vb, 0)); -#endif - - spin_lock_irq(&mx3_cam->lock); - list_add_tail(&buf->queue, &mx3_cam->capture); - - if (!mx3_cam->active) - mx3_cam->active = buf; - - spin_unlock_irq(&mx3_cam->lock); - - cookie = txd->tx_submit(txd); - dev_dbg(icd->parent, "Submitted cookie %d DMA %pad\n", - cookie, &sg_dma_address(&buf->sg)); - - if (cookie >= 0) - return; - - spin_lock_irq(&mx3_cam->lock); - - /* Submit error */ - list_del_init(&buf->queue); - - if (mx3_cam->active == buf) - mx3_cam->active = NULL; - - spin_unlock_irq(&mx3_cam->lock); -error: - vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); -} - -static void mx3_videobuf_release(struct vb2_buffer *vb) -{ - struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); - struct soc_camera_device *icd = soc_camera_from_vb2q(vb->vb2_queue); - struct soc_camera_host *ici = to_soc_camera_host(icd->parent); - struct mx3_camera_dev *mx3_cam = ici->priv; - struct mx3_camera_buffer *buf = to_mx3_vb(vbuf); - struct dma_async_tx_descriptor *txd = buf->txd; - unsigned long flags; - - dev_dbg(icd->parent, - "Release%s DMA %pad, queue %sempty\n", - mx3_cam->active == buf ? " active" : "", &sg_dma_address(&buf->sg), - list_empty(&buf->queue) ? "" : "not "); - - spin_lock_irqsave(&mx3_cam->lock, flags); - - if (mx3_cam->active == buf) - mx3_cam->active = NULL; - - /* Doesn't hurt also if the list is empty */ - list_del_init(&buf->queue); - - if (txd) { - buf->txd = NULL; - if (mx3_cam->idmac_channel[0]) - async_tx_ack(txd); - } - - spin_unlock_irqrestore(&mx3_cam->lock, flags); - - mx3_cam->buf_total -= vb2_plane_size(vb, 0); -} - -static int mx3_videobuf_init(struct vb2_buffer *vb) -{ - struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); - struct soc_camera_device *icd = soc_camera_from_vb2q(vb->vb2_queue); - struct soc_camera_host *ici = to_soc_camera_host(icd->parent); - struct mx3_camera_dev *mx3_cam = ici->priv; - struct mx3_camera_buffer *buf = to_mx3_vb(vbuf); - - if (!buf->txd) { - /* This is for locking debugging only */ - INIT_LIST_HEAD(&buf->queue); - sg_init_table(&buf->sg, 1); - - mx3_cam->buf_total += vb2_plane_size(vb, 0); - } - - return 0; -} - -static void mx3_stop_streaming(struct vb2_queue *q) -{ - struct soc_camera_device *icd = soc_camera_from_vb2q(q); - struct soc_camera_host *ici = to_soc_camera_host(icd->parent); - struct mx3_camera_dev *mx3_cam = ici->priv; - struct idmac_channel *ichan = mx3_cam->idmac_channel[0]; - struct mx3_camera_buffer *buf, *tmp; - unsigned long flags; - - if (ichan) - dmaengine_pause(&ichan->dma_chan); - - spin_lock_irqsave(&mx3_cam->lock, flags); - - mx3_cam->active = NULL; - - list_for_each_entry_safe(buf, tmp, &mx3_cam->capture, queue) { - list_del_init(&buf->queue); - vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); - } - - spin_unlock_irqrestore(&mx3_cam->lock, flags); -} - -static struct vb2_ops mx3_videobuf_ops = { - .queue_setup = mx3_videobuf_setup, - .buf_queue = mx3_videobuf_queue, - .buf_cleanup = mx3_videobuf_release, - .buf_init = mx3_videobuf_init, - .wait_prepare = vb2_ops_wait_prepare, - .wait_finish = vb2_ops_wait_finish, - .stop_streaming = mx3_stop_streaming, -}; - -static int mx3_camera_init_videobuf(struct vb2_queue *q, - struct soc_camera_device *icd) -{ - struct soc_camera_host *ici = to_soc_camera_host(icd->parent); - - q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - q->io_modes = VB2_MMAP | VB2_USERPTR; - q->drv_priv = icd; - q->ops = &mx3_videobuf_ops; - q->mem_ops = &vb2_dma_contig_memops; - q->buf_struct_size = sizeof(struct mx3_camera_buffer); - q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; - q->lock = &ici->host_lock; - - return vb2_queue_init(q); -} - -/* First part of ipu_csi_init_interface() */ -static void mx3_camera_activate(struct mx3_camera_dev *mx3_cam) -{ - u32 conf; - long rate; - - /* Set default size: ipu_csi_set_window_size() */ - csi_reg_write(mx3_cam, (640 - 1) | ((480 - 1) << 16), CSI_ACT_FRM_SIZE); - /* ...and position to 0:0: ipu_csi_set_window_pos() */ - conf = csi_reg_read(mx3_cam, CSI_OUT_FRM_CTRL) & 0xffff0000; - csi_reg_write(mx3_cam, conf, CSI_OUT_FRM_CTRL); - - /* We use only gated clock synchronisation mode so far */ - conf = 0 << CSI_SENS_CONF_SENS_PRTCL_SHIFT; - - /* Set generic data, platform-biggest bus-width */ - conf |= CSI_SENS_CONF_DATA_FMT_BAYER; - - if (mx3_cam->platform_flags & MX3_CAMERA_DATAWIDTH_15) - conf |= 3 << CSI_SENS_CONF_DATA_WIDTH_SHIFT; - else if (mx3_cam->platform_flags & MX3_CAMERA_DATAWIDTH_10) - conf |= 2 << CSI_SENS_CONF_DATA_WIDTH_SHIFT; - else if (mx3_cam->platform_flags & MX3_CAMERA_DATAWIDTH_8) - conf |= 1 << CSI_SENS_CONF_DATA_WIDTH_SHIFT; - else/* if (mx3_cam->platform_flags & MX3_CAMERA_DATAWIDTH_4)*/ - conf |= 0 << CSI_SENS_CONF_DATA_WIDTH_SHIFT; - - if (mx3_cam->platform_flags & MX3_CAMERA_CLK_SRC) - conf |= 1 << CSI_SENS_CONF_SENS_CLKSRC_SHIFT; - if (mx3_cam->platform_flags & MX3_CAMERA_EXT_VSYNC) - conf |= 1 << CSI_SENS_CONF_EXT_VSYNC_SHIFT; - if (mx3_cam->platform_flags & MX3_CAMERA_DP) - conf |= 1 << CSI_SENS_CONF_DATA_POL_SHIFT; - if (mx3_cam->platform_flags & MX3_CAMERA_PCP) - conf |= 1 << CSI_SENS_CONF_PIX_CLK_POL_SHIFT; - if (mx3_cam->platform_flags & MX3_CAMERA_HSP) - conf |= 1 << CSI_SENS_CONF_HSYNC_POL_SHIFT; - if (mx3_cam->platform_flags & MX3_CAMERA_VSP) - conf |= 1 << CSI_SENS_CONF_VSYNC_POL_SHIFT; - - /* ipu_csi_init_interface() */ - csi_reg_write(mx3_cam, conf, CSI_SENS_CONF); - - clk_prepare_enable(mx3_cam->clk); - rate = clk_round_rate(mx3_cam->clk, mx3_cam->mclk); - dev_dbg(mx3_cam->soc_host.v4l2_dev.dev, "Set SENS_CONF to %x, rate %ld\n", conf, rate); - if (rate) - clk_set_rate(mx3_cam->clk, rate); -} - -static int mx3_camera_add_device(struct soc_camera_device *icd) -{ - dev_info(icd->parent, "MX3 Camera driver attached to camera %d\n", - icd->devnum); - - return 0; -} - -static void mx3_camera_remove_device(struct soc_camera_device *icd) -{ - dev_info(icd->parent, "MX3 Camera driver detached from camera %d\n", - icd->devnum); -} - -/* Called with .host_lock held */ -static int mx3_camera_clock_start(struct soc_camera_host *ici) -{ - struct mx3_camera_dev *mx3_cam = ici->priv; - - mx3_camera_activate(mx3_cam); - - mx3_cam->buf_total = 0; - - return 0; -} - -/* Called with .host_lock held */ -static void mx3_camera_clock_stop(struct soc_camera_host *ici) -{ - struct mx3_camera_dev *mx3_cam = ici->priv; - struct idmac_channel **ichan = &mx3_cam->idmac_channel[0]; - - if (*ichan) { - dma_release_channel(&(*ichan)->dma_chan); - *ichan = NULL; - } - - clk_disable_unprepare(mx3_cam->clk); -} - -static int test_platform_param(struct mx3_camera_dev *mx3_cam, - unsigned char buswidth, unsigned long *flags) -{ - /* - * If requested data width is supported by the platform, use it or any - * possible lower value - i.MX31 is smart enough to shift bits - */ - if (buswidth > fls(mx3_cam->width_flags)) - return -EINVAL; - - /* - * Platform specified synchronization and pixel clock polarities are - * only a recommendation and are only used during probing. MX3x - * camera interface only works in master mode, i.e., uses HSYNC and - * VSYNC signals from the sensor - */ - *flags = V4L2_MBUS_MASTER | - V4L2_MBUS_HSYNC_ACTIVE_HIGH | - V4L2_MBUS_HSYNC_ACTIVE_LOW | - V4L2_MBUS_VSYNC_ACTIVE_HIGH | - V4L2_MBUS_VSYNC_ACTIVE_LOW | - V4L2_MBUS_PCLK_SAMPLE_RISING | - V4L2_MBUS_PCLK_SAMPLE_FALLING | - V4L2_MBUS_DATA_ACTIVE_HIGH | - V4L2_MBUS_DATA_ACTIVE_LOW; - - return 0; -} - -static int mx3_camera_try_bus_param(struct soc_camera_device *icd, - const unsigned int depth) -{ - struct v4l2_subdev *sd = soc_camera_to_subdev(icd); - struct soc_camera_host *ici = to_soc_camera_host(icd->parent); - struct mx3_camera_dev *mx3_cam = ici->priv; - struct v4l2_mbus_config cfg = {.type = V4L2_MBUS_PARALLEL,}; - unsigned long bus_flags, common_flags; - int ret = test_platform_param(mx3_cam, depth, &bus_flags); - - dev_dbg(icd->parent, "request bus width %d bit: %d\n", depth, ret); - - if (ret < 0) - return ret; - - ret = v4l2_subdev_call(sd, video, g_mbus_config, &cfg); - if (!ret) { - common_flags = soc_mbus_config_compatible(&cfg, - bus_flags); - if (!common_flags) { - dev_warn(icd->parent, - "Flags incompatible: camera 0x%x, host 0x%lx\n", - cfg.flags, bus_flags); - return -EINVAL; - } - } else if (ret != -ENOIOCTLCMD) { - return ret; - } - - return 0; -} - -static bool chan_filter(struct dma_chan *chan, void *arg) -{ - struct dma_chan_request *rq = arg; - struct mx3_camera_pdata *pdata; - - if (!imx_dma_is_ipu(chan)) - return false; - - if (!rq) - return false; - - pdata = rq->mx3_cam->soc_host.v4l2_dev.dev->platform_data; - - return rq->id == chan->chan_id && - pdata->dma_dev == chan->device->dev; -} - -static const struct soc_mbus_pixelfmt mx3_camera_formats[] = { - { - .fourcc = V4L2_PIX_FMT_SBGGR8, - .name = "Bayer BGGR (sRGB) 8 bit", - .bits_per_sample = 8, - .packing = SOC_MBUS_PACKING_NONE, - .order = SOC_MBUS_ORDER_LE, - .layout = SOC_MBUS_LAYOUT_PACKED, - }, { - .fourcc = V4L2_PIX_FMT_GREY, - .name = "Monochrome 8 bit", - .bits_per_sample = 8, - .packing = SOC_MBUS_PACKING_NONE, - .order = SOC_MBUS_ORDER_LE, - .layout = SOC_MBUS_LAYOUT_PACKED, - }, -}; - -/* This will be corrected as we get more formats */ -static bool mx3_camera_packing_supported(const struct soc_mbus_pixelfmt *fmt) -{ - return fmt->packing == SOC_MBUS_PACKING_NONE || - (fmt->bits_per_sample == 8 && - fmt->packing == SOC_MBUS_PACKING_2X8_PADHI) || - (fmt->bits_per_sample > 8 && - fmt->packing == SOC_MBUS_PACKING_EXTEND16); -} - -static int mx3_camera_get_formats(struct soc_camera_device *icd, unsigned int idx, - struct soc_camera_format_xlate *xlate) -{ - struct v4l2_subdev *sd = soc_camera_to_subdev(icd); - struct device *dev = icd->parent; - int formats = 0, ret; - struct v4l2_subdev_mbus_code_enum code = { - .which = V4L2_SUBDEV_FORMAT_ACTIVE, - .index = idx, - }; - const struct soc_mbus_pixelfmt *fmt; - - ret = v4l2_subdev_call(sd, pad, enum_mbus_code, NULL, &code); - if (ret < 0) - /* No more formats */ - return 0; - - fmt = soc_mbus_get_fmtdesc(code.code); - if (!fmt) { - dev_warn(icd->parent, - "Unsupported format code #%u: 0x%x\n", idx, code.code); - return 0; - } - - /* This also checks support for the requested bits-per-sample */ - ret = mx3_camera_try_bus_param(icd, fmt->bits_per_sample); - if (ret < 0) - return 0; - - switch (code.code) { - case MEDIA_BUS_FMT_SBGGR10_1X10: - formats++; - if (xlate) { - xlate->host_fmt = &mx3_camera_formats[0]; - xlate->code = code.code; - xlate++; - dev_dbg(dev, "Providing format %s using code 0x%x\n", - mx3_camera_formats[0].name, code.code); - } - break; - case MEDIA_BUS_FMT_Y10_1X10: - formats++; - if (xlate) { - xlate->host_fmt = &mx3_camera_formats[1]; - xlate->code = code.code; - xlate++; - dev_dbg(dev, "Providing format %s using code 0x%x\n", - mx3_camera_formats[1].name, code.code); - } - break; - default: - if (!mx3_camera_packing_supported(fmt)) - return 0; - } - - /* Generic pass-through */ - formats++; - if (xlate) { - xlate->host_fmt = fmt; - xlate->code = code.code; - dev_dbg(dev, "Providing format %c%c%c%c in pass-through mode\n", - (fmt->fourcc >> (0*8)) & 0xFF, - (fmt->fourcc >> (1*8)) & 0xFF, - (fmt->fourcc >> (2*8)) & 0xFF, - (fmt->fourcc >> (3*8)) & 0xFF); - xlate++; - } - - return formats; -} - -static void configure_geometry(struct mx3_camera_dev *mx3_cam, - unsigned int width, unsigned int height, - const struct soc_mbus_pixelfmt *fmt) -{ - u32 ctrl, width_field, height_field; - - if (fourcc_to_ipu_pix(fmt->fourcc) == IPU_PIX_FMT_GENERIC) { - /* - * As the CSI will be configured to output BAYER, here - * the width parameter count the number of samples to - * capture to complete the whole image width. - */ - unsigned int num, den; - int ret = soc_mbus_samples_per_pixel(fmt, &num, &den); - BUG_ON(ret < 0); - width = width * num / den; - } - - /* Setup frame size - this cannot be changed on-the-fly... */ - width_field = width - 1; - height_field = height - 1; - csi_reg_write(mx3_cam, width_field | (height_field << 16), CSI_SENS_FRM_SIZE); - - csi_reg_write(mx3_cam, width_field << 16, CSI_FLASH_STROBE_1); - csi_reg_write(mx3_cam, (height_field << 16) | 0x22, CSI_FLASH_STROBE_2); - - csi_reg_write(mx3_cam, width_field | (height_field << 16), CSI_ACT_FRM_SIZE); - - /* ...and position */ - ctrl = csi_reg_read(mx3_cam, CSI_OUT_FRM_CTRL) & 0xffff0000; - /* Sensor does the cropping */ - csi_reg_write(mx3_cam, ctrl | 0 | (0 << 8), CSI_OUT_FRM_CTRL); -} - -static int acquire_dma_channel(struct mx3_camera_dev *mx3_cam) -{ - dma_cap_mask_t mask; - struct dma_chan *chan; - struct idmac_channel **ichan = &mx3_cam->idmac_channel[0]; - /* We have to use IDMAC_IC_7 for Bayer / generic data */ - struct dma_chan_request rq = {.mx3_cam = mx3_cam, - .id = IDMAC_IC_7}; - - dma_cap_zero(mask); - dma_cap_set(DMA_SLAVE, mask); - dma_cap_set(DMA_PRIVATE, mask); - chan = dma_request_channel(mask, chan_filter, &rq); - if (!chan) - return -EBUSY; - - *ichan = to_idmac_chan(chan); - (*ichan)->client = mx3_cam; - - return 0; -} - -/* - * FIXME: learn to use stride != width, then we can keep stride properly aligned - * and support arbitrary (even) widths. - */ -static inline void stride_align(__u32 *width) -{ - if (ALIGN(*width, 8) < 4096) - *width = ALIGN(*width, 8); - else - *width = *width & ~7; -} - -/* - * As long as we don't implement host-side cropping and scaling, we can use - * default g_crop and cropcap from soc_camera.c - */ -static int mx3_camera_set_crop(struct soc_camera_device *icd, - const struct v4l2_crop *a) -{ - struct v4l2_crop a_writable = *a; - struct v4l2_rect *rect = &a_writable.c; - struct soc_camera_host *ici = to_soc_camera_host(icd->parent); - struct mx3_camera_dev *mx3_cam = ici->priv; - struct v4l2_subdev *sd = soc_camera_to_subdev(icd); - struct v4l2_subdev_format fmt = { - .which = V4L2_SUBDEV_FORMAT_ACTIVE, - }; - struct v4l2_mbus_framefmt *mf = &fmt.format; - int ret; - - soc_camera_limit_side(&rect->left, &rect->width, 0, 2, 4096); - soc_camera_limit_side(&rect->top, &rect->height, 0, 2, 4096); - - ret = v4l2_subdev_call(sd, video, s_crop, a); - if (ret < 0) - return ret; - - /* The capture device might have changed its output sizes */ - ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &fmt); - if (ret < 0) - return ret; - - if (mf->code != icd->current_fmt->code) - return -EINVAL; - - if (mf->width & 7) { - /* Ouch! We can only handle 8-byte aligned width... */ - stride_align(&mf->width); - ret = v4l2_subdev_call(sd, pad, set_fmt, NULL, &fmt); - if (ret < 0) - return ret; - } - - if (mf->width != icd->user_width || mf->height != icd->user_height) - configure_geometry(mx3_cam, mf->width, mf->height, - icd->current_fmt->host_fmt); - - dev_dbg(icd->parent, "Sensor cropped %dx%d\n", - mf->width, mf->height); - - icd->user_width = mf->width; - icd->user_height = mf->height; - - return ret; -} - -static int mx3_camera_set_fmt(struct soc_camera_device *icd, - struct v4l2_format *f) -{ - struct soc_camera_host *ici = to_soc_camera_host(icd->parent); - struct mx3_camera_dev *mx3_cam = ici->priv; - struct v4l2_subdev *sd = soc_camera_to_subdev(icd); - const struct soc_camera_format_xlate *xlate; - struct v4l2_pix_format *pix = &f->fmt.pix; - struct v4l2_subdev_format format = { - .which = V4L2_SUBDEV_FORMAT_ACTIVE, - }; - struct v4l2_mbus_framefmt *mf = &format.format; - int ret; - - xlate = soc_camera_xlate_by_fourcc(icd, pix->pixelformat); - if (!xlate) { - dev_warn(icd->parent, "Format %x not found\n", - pix->pixelformat); - return -EINVAL; - } - - stride_align(&pix->width); - dev_dbg(icd->parent, "Set format %dx%d\n", pix->width, pix->height); - - /* - * Might have to perform a complete interface initialisation like in - * ipu_csi_init_interface() in mxc_v4l2_s_param(). Also consider - * mxc_v4l2_s_fmt() - */ - - configure_geometry(mx3_cam, pix->width, pix->height, xlate->host_fmt); - - mf->width = pix->width; - mf->height = pix->height; - mf->field = pix->field; - mf->colorspace = pix->colorspace; - mf->code = xlate->code; - - ret = v4l2_subdev_call(sd, pad, set_fmt, NULL, &format); - if (ret < 0) - return ret; - - if (mf->code != xlate->code) - return -EINVAL; - - if (!mx3_cam->idmac_channel[0]) { - ret = acquire_dma_channel(mx3_cam); - if (ret < 0) - return ret; - } - - pix->width = mf->width; - pix->height = mf->height; - pix->field = mf->field; - mx3_cam->field = mf->field; - pix->colorspace = mf->colorspace; - icd->current_fmt = xlate; - - dev_dbg(icd->parent, "Sensor set %dx%d\n", pix->width, pix->height); - - return ret; -} - -static int mx3_camera_try_fmt(struct soc_camera_device *icd, - struct v4l2_format *f) -{ - struct v4l2_subdev *sd = soc_camera_to_subdev(icd); - const struct soc_camera_format_xlate *xlate; - struct v4l2_pix_format *pix = &f->fmt.pix; - struct v4l2_subdev_pad_config pad_cfg; - struct v4l2_subdev_format format = { - .which = V4L2_SUBDEV_FORMAT_TRY, - }; - struct v4l2_mbus_framefmt *mf = &format.format; - __u32 pixfmt = pix->pixelformat; - int ret; - - xlate = soc_camera_xlate_by_fourcc(icd, pixfmt); - if (pixfmt && !xlate) { - dev_warn(icd->parent, "Format %x not found\n", pixfmt); - return -EINVAL; - } - - /* limit to MX3 hardware capabilities */ - if (pix->height > 4096) - pix->height = 4096; - if (pix->width > 4096) - pix->width = 4096; - - /* limit to sensor capabilities */ - mf->width = pix->width; - mf->height = pix->height; - mf->field = pix->field; - mf->colorspace = pix->colorspace; - mf->code = xlate->code; - - ret = v4l2_subdev_call(sd, pad, set_fmt, &pad_cfg, &format); - if (ret < 0) - return ret; - - pix->width = mf->width; - pix->height = mf->height; - pix->colorspace = mf->colorspace; - - switch (mf->field) { - case V4L2_FIELD_ANY: - pix->field = V4L2_FIELD_NONE; - break; - case V4L2_FIELD_NONE: - break; - default: - dev_err(icd->parent, "Field type %d unsupported.\n", - mf->field); - ret = -EINVAL; - } - - return ret; -} - -static int mx3_camera_reqbufs(struct soc_camera_device *icd, - struct v4l2_requestbuffers *p) -{ - return 0; -} - -static unsigned int mx3_camera_poll(struct file *file, poll_table *pt) -{ - struct soc_camera_device *icd = file->private_data; - - return vb2_poll(&icd->vb2_vidq, file, pt); -} - -static int mx3_camera_querycap(struct soc_camera_host *ici, - struct v4l2_capability *cap) -{ - /* cap->name is set by the firendly caller:-> */ - strlcpy(cap->card, "i.MX3x Camera", sizeof(cap->card)); - cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; - cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; - - return 0; -} - -static int mx3_camera_set_bus_param(struct soc_camera_device *icd) -{ - struct v4l2_subdev *sd = soc_camera_to_subdev(icd); - struct soc_camera_host *ici = to_soc_camera_host(icd->parent); - struct mx3_camera_dev *mx3_cam = ici->priv; - struct v4l2_mbus_config cfg = {.type = V4L2_MBUS_PARALLEL,}; - u32 pixfmt = icd->current_fmt->host_fmt->fourcc; - unsigned long bus_flags, common_flags; - u32 dw, sens_conf; - const struct soc_mbus_pixelfmt *fmt; - int buswidth; - int ret; - const struct soc_camera_format_xlate *xlate; - struct device *dev = icd->parent; - - fmt = soc_mbus_get_fmtdesc(icd->current_fmt->code); - if (!fmt) - return -EINVAL; - - xlate = soc_camera_xlate_by_fourcc(icd, pixfmt); - if (!xlate) { - dev_warn(dev, "Format %x not found\n", pixfmt); - return -EINVAL; - } - - buswidth = fmt->bits_per_sample; - ret = test_platform_param(mx3_cam, buswidth, &bus_flags); - - dev_dbg(dev, "requested bus width %d bit: %d\n", buswidth, ret); - - if (ret < 0) - return ret; - - ret = v4l2_subdev_call(sd, video, g_mbus_config, &cfg); - if (!ret) { - common_flags = soc_mbus_config_compatible(&cfg, - bus_flags); - if (!common_flags) { - dev_warn(icd->parent, - "Flags incompatible: camera 0x%x, host 0x%lx\n", - cfg.flags, bus_flags); - return -EINVAL; - } - } else if (ret != -ENOIOCTLCMD) { - return ret; - } else { - common_flags = bus_flags; - } - - dev_dbg(dev, "Flags cam: 0x%x host: 0x%lx common: 0x%lx\n", - cfg.flags, bus_flags, common_flags); - - /* Make choices, based on platform preferences */ - if ((common_flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) && - (common_flags & V4L2_MBUS_HSYNC_ACTIVE_LOW)) { - if (mx3_cam->platform_flags & MX3_CAMERA_HSP) - common_flags &= ~V4L2_MBUS_HSYNC_ACTIVE_HIGH; - else - common_flags &= ~V4L2_MBUS_HSYNC_ACTIVE_LOW; - } - - if ((common_flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) && - (common_flags & V4L2_MBUS_VSYNC_ACTIVE_LOW)) { - if (mx3_cam->platform_flags & MX3_CAMERA_VSP) - common_flags &= ~V4L2_MBUS_VSYNC_ACTIVE_HIGH; - else - common_flags &= ~V4L2_MBUS_VSYNC_ACTIVE_LOW; - } - - if ((common_flags & V4L2_MBUS_DATA_ACTIVE_HIGH) && - (common_flags & V4L2_MBUS_DATA_ACTIVE_LOW)) { - if (mx3_cam->platform_flags & MX3_CAMERA_DP) - common_flags &= ~V4L2_MBUS_DATA_ACTIVE_HIGH; - else - common_flags &= ~V4L2_MBUS_DATA_ACTIVE_LOW; - } - - if ((common_flags & V4L2_MBUS_PCLK_SAMPLE_RISING) && - (common_flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)) { - if (mx3_cam->platform_flags & MX3_CAMERA_PCP) - common_flags &= ~V4L2_MBUS_PCLK_SAMPLE_RISING; - else - common_flags &= ~V4L2_MBUS_PCLK_SAMPLE_FALLING; - } - - cfg.flags = common_flags; - ret = v4l2_subdev_call(sd, video, s_mbus_config, &cfg); - if (ret < 0 && ret != -ENOIOCTLCMD) { - dev_dbg(dev, "camera s_mbus_config(0x%lx) returned %d\n", - common_flags, ret); - return ret; - } - - /* - * So far only gated clock mode is supported. Add a line - * (3 << CSI_SENS_CONF_SENS_PRTCL_SHIFT) | - * below and select the required mode when supporting other - * synchronisation protocols. - */ - sens_conf = csi_reg_read(mx3_cam, CSI_SENS_CONF) & - ~((1 << CSI_SENS_CONF_VSYNC_POL_SHIFT) | - (1 << CSI_SENS_CONF_HSYNC_POL_SHIFT) | - (1 << CSI_SENS_CONF_DATA_POL_SHIFT) | - (1 << CSI_SENS_CONF_PIX_CLK_POL_SHIFT) | - (3 << CSI_SENS_CONF_DATA_FMT_SHIFT) | - (3 << CSI_SENS_CONF_DATA_WIDTH_SHIFT)); - - /* TODO: Support RGB and YUV formats */ - - /* This has been set in mx3_camera_activate(), but we clear it above */ - sens_conf |= CSI_SENS_CONF_DATA_FMT_BAYER; - - if (common_flags & V4L2_MBUS_PCLK_SAMPLE_FALLING) - sens_conf |= 1 << CSI_SENS_CONF_PIX_CLK_POL_SHIFT; - if (common_flags & V4L2_MBUS_HSYNC_ACTIVE_LOW) - sens_conf |= 1 << CSI_SENS_CONF_HSYNC_POL_SHIFT; - if (common_flags & V4L2_MBUS_VSYNC_ACTIVE_LOW) - sens_conf |= 1 << CSI_SENS_CONF_VSYNC_POL_SHIFT; - if (common_flags & V4L2_MBUS_DATA_ACTIVE_LOW) - sens_conf |= 1 << CSI_SENS_CONF_DATA_POL_SHIFT; - - /* Just do what we're asked to do */ - switch (xlate->host_fmt->bits_per_sample) { - case 4: - dw = 0 << CSI_SENS_CONF_DATA_WIDTH_SHIFT; - break; - case 8: - dw = 1 << CSI_SENS_CONF_DATA_WIDTH_SHIFT; - break; - case 10: - dw = 2 << CSI_SENS_CONF_DATA_WIDTH_SHIFT; - break; - default: - /* - * Actually it can only be 15 now, default is just to silence - * compiler warnings - */ - case 15: - dw = 3 << CSI_SENS_CONF_DATA_WIDTH_SHIFT; - } - - csi_reg_write(mx3_cam, sens_conf | dw, CSI_SENS_CONF); - - dev_dbg(dev, "Set SENS_CONF to %x\n", sens_conf | dw); - - return 0; -} - -static struct soc_camera_host_ops mx3_soc_camera_host_ops = { - .owner = THIS_MODULE, - .add = mx3_camera_add_device, - .remove = mx3_camera_remove_device, - .clock_start = mx3_camera_clock_start, - .clock_stop = mx3_camera_clock_stop, - .set_crop = mx3_camera_set_crop, - .set_fmt = mx3_camera_set_fmt, - .try_fmt = mx3_camera_try_fmt, - .get_formats = mx3_camera_get_formats, - .init_videobuf2 = mx3_camera_init_videobuf, - .reqbufs = mx3_camera_reqbufs, - .poll = mx3_camera_poll, - .querycap = mx3_camera_querycap, - .set_bus_param = mx3_camera_set_bus_param, -}; - -static int mx3_camera_probe(struct platform_device *pdev) -{ - struct mx3_camera_pdata *pdata = pdev->dev.platform_data; - struct mx3_camera_dev *mx3_cam; - struct resource *res; - void __iomem *base; - int err = 0; - struct soc_camera_host *soc_host; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(base)) - return PTR_ERR(base); - - if (!pdata) - return -EINVAL; - - mx3_cam = devm_kzalloc(&pdev->dev, sizeof(*mx3_cam), GFP_KERNEL); - if (!mx3_cam) { - dev_err(&pdev->dev, "Could not allocate mx3 camera object\n"); - return -ENOMEM; - } - - mx3_cam->clk = devm_clk_get(&pdev->dev, NULL); - if (IS_ERR(mx3_cam->clk)) - return PTR_ERR(mx3_cam->clk); - - mx3_cam->pdata = pdata; - mx3_cam->platform_flags = pdata->flags; - if (!(mx3_cam->platform_flags & MX3_CAMERA_DATAWIDTH_MASK)) { - /* - * Platform hasn't set available data widths. This is bad. - * Warn and use a default. - */ - dev_warn(&pdev->dev, "WARNING! Platform hasn't set available " - "data widths, using default 8 bit\n"); - mx3_cam->platform_flags |= MX3_CAMERA_DATAWIDTH_8; - } - if (mx3_cam->platform_flags & MX3_CAMERA_DATAWIDTH_4) - mx3_cam->width_flags = 1 << 3; - if (mx3_cam->platform_flags & MX3_CAMERA_DATAWIDTH_8) - mx3_cam->width_flags |= 1 << 7; - if (mx3_cam->platform_flags & MX3_CAMERA_DATAWIDTH_10) - mx3_cam->width_flags |= 1 << 9; - if (mx3_cam->platform_flags & MX3_CAMERA_DATAWIDTH_15) - mx3_cam->width_flags |= 1 << 14; - - mx3_cam->mclk = pdata->mclk_10khz * 10000; - if (!mx3_cam->mclk) { - dev_warn(&pdev->dev, - "mclk_10khz == 0! Please, fix your platform data. " - "Using default 20MHz\n"); - mx3_cam->mclk = 20000000; - } - - /* list of video-buffers */ - INIT_LIST_HEAD(&mx3_cam->capture); - spin_lock_init(&mx3_cam->lock); - - mx3_cam->base = base; - - soc_host = &mx3_cam->soc_host; - soc_host->drv_name = MX3_CAM_DRV_NAME; - soc_host->ops = &mx3_soc_camera_host_ops; - soc_host->priv = mx3_cam; - soc_host->v4l2_dev.dev = &pdev->dev; - soc_host->nr = pdev->id; - - mx3_cam->alloc_ctx = vb2_dma_contig_init_ctx(&pdev->dev); - if (IS_ERR(mx3_cam->alloc_ctx)) - return PTR_ERR(mx3_cam->alloc_ctx); - - if (pdata->asd_sizes) { - soc_host->asd = pdata->asd; - soc_host->asd_sizes = pdata->asd_sizes; - } - - err = soc_camera_host_register(soc_host); - if (err) - goto ecamhostreg; - - /* IDMAC interface */ - dmaengine_get(); - - return 0; - -ecamhostreg: - vb2_dma_contig_cleanup_ctx(mx3_cam->alloc_ctx); - return err; -} - -static int mx3_camera_remove(struct platform_device *pdev) -{ - struct soc_camera_host *soc_host = to_soc_camera_host(&pdev->dev); - struct mx3_camera_dev *mx3_cam = container_of(soc_host, - struct mx3_camera_dev, soc_host); - - soc_camera_host_unregister(soc_host); - - /* - * The channel has either not been allocated, - * or should have been released - */ - if (WARN_ON(mx3_cam->idmac_channel[0])) - dma_release_channel(&mx3_cam->idmac_channel[0]->dma_chan); - - vb2_dma_contig_cleanup_ctx(mx3_cam->alloc_ctx); - - dmaengine_put(); - - return 0; -} - -static struct platform_driver mx3_camera_driver = { - .driver = { - .name = MX3_CAM_DRV_NAME, - }, - .probe = mx3_camera_probe, - .remove = mx3_camera_remove, -}; - -module_platform_driver(mx3_camera_driver); - -MODULE_DESCRIPTION("i.MX3x SoC Camera Host driver"); -MODULE_AUTHOR("Guennadi Liakhovetski <lg@denx.de>"); -MODULE_LICENSE("GPL v2"); -MODULE_VERSION("0.2.3"); -MODULE_ALIAS("platform:" MX3_CAM_DRV_NAME); diff --git a/drivers/staging/media/omap1/Kconfig b/drivers/staging/media/omap1/Kconfig deleted file mode 100644 index 6cfab3a04ae1..000000000000 --- a/drivers/staging/media/omap1/Kconfig +++ /dev/null @@ -1,13 +0,0 @@ -config VIDEO_OMAP1 - tristate "OMAP1 Camera Interface driver" - depends on VIDEO_DEV && SOC_CAMERA - depends on ARCH_OMAP1 - depends on HAS_DMA - select VIDEOBUF_DMA_CONTIG - select VIDEOBUF_DMA_SG - ---help--- - This is a v4l2 driver for the TI OMAP1 camera interface - - This driver is deprecated and will be removed soon unless someone - will start the work to convert this driver to the vb2 framework - and remove the soc-camera dependency. diff --git a/drivers/staging/media/omap1/Makefile b/drivers/staging/media/omap1/Makefile deleted file mode 100644 index 2885622600f2..000000000000 --- a/drivers/staging/media/omap1/Makefile +++ /dev/null @@ -1,3 +0,0 @@ -# Makefile for OMAP1 driver - -obj-$(CONFIG_VIDEO_OMAP1) += omap1_camera.o diff --git a/drivers/staging/media/omap1/TODO b/drivers/staging/media/omap1/TODO deleted file mode 100644 index 1025f9f60ff0..000000000000 --- a/drivers/staging/media/omap1/TODO +++ /dev/null @@ -1,8 +0,0 @@ -This driver is deprecated and will be removed soon unless someone will start -the work to convert this driver to the vb2 framework and remove the -soc-camera dependency. - -Note that trivial patches will not be accepted anymore, only a full conversion. - -If you want to convert this driver, please contact the linux-media mailinglist -(see http://linuxtv.org/lists.php). diff --git a/drivers/staging/media/omap1/omap1_camera.c b/drivers/staging/media/omap1/omap1_camera.c deleted file mode 100644 index 54b8dd2d2bba..000000000000 --- a/drivers/staging/media/omap1/omap1_camera.c +++ /dev/null @@ -1,1702 +0,0 @@ -/* - * V4L2 SoC Camera driver for OMAP1 Camera Interface - * - * Copyright (C) 2010, Janusz Krzysztofik <jkrzyszt@tis.icnet.pl> - * - * Based on V4L2 Driver for i.MXL/i.MXL camera (CSI) host - * Copyright (C) 2008, Paulius Zaleckas <paulius.zaleckas@teltonika.lt> - * Copyright (C) 2009, Darius Augulis <augulis.darius@gmail.com> - * - * Based on PXA SoC camera driver - * Copyright (C) 2006, Sascha Hauer, Pengutronix - * Copyright (C) 2008, Guennadi Liakhovetski <kernel@pengutronix.de> - * - * Hardware specific bits initialy based on former work by Matt Callow - * drivers/media/platform/omap/omap1510cam.c - * Copyright (C) 2006 Matt Callow - * - * 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. - */ - - -#include <linux/clk.h> -#include <linux/dma-mapping.h> -#include <linux/interrupt.h> -#include <linux/module.h> -#include <linux/platform_device.h> -#include <linux/slab.h> - -#include <linux/platform_data/media/omap1_camera.h> -#include <media/soc_camera.h> -#include <media/drv-intf/soc_mediabus.h> -#include <media/videobuf-dma-contig.h> -#include <media/videobuf-dma-sg.h> - -#include <linux/omap-dma.h> - - -#define DRIVER_NAME "omap1-camera" -#define DRIVER_VERSION "0.0.2" - -#define OMAP_DMA_CAMERA_IF_RX 20 - -/* - * --------------------------------------------------------------------------- - * OMAP1 Camera Interface registers - * --------------------------------------------------------------------------- - */ - -#define REG_CTRLCLOCK 0x00 -#define REG_IT_STATUS 0x04 -#define REG_MODE 0x08 -#define REG_STATUS 0x0C -#define REG_CAMDATA 0x10 -#define REG_GPIO 0x14 -#define REG_PEAK_COUNTER 0x18 - -/* CTRLCLOCK bit shifts */ -#define LCLK_EN BIT(7) -#define DPLL_EN BIT(6) -#define MCLK_EN BIT(5) -#define CAMEXCLK_EN BIT(4) -#define POLCLK BIT(3) -#define FOSCMOD_SHIFT 0 -#define FOSCMOD_MASK (0x7 << FOSCMOD_SHIFT) -#define FOSCMOD_12MHz 0x0 -#define FOSCMOD_6MHz 0x2 -#define FOSCMOD_9_6MHz 0x4 -#define FOSCMOD_24MHz 0x5 -#define FOSCMOD_8MHz 0x6 - -/* IT_STATUS bit shifts */ -#define DATA_TRANSFER BIT(5) -#define FIFO_FULL BIT(4) -#define H_DOWN BIT(3) -#define H_UP BIT(2) -#define V_DOWN BIT(1) -#define V_UP BIT(0) - -/* MODE bit shifts */ -#define RAZ_FIFO BIT(18) -#define EN_FIFO_FULL BIT(17) -#define EN_NIRQ BIT(16) -#define THRESHOLD_SHIFT 9 -#define THRESHOLD_MASK (0x7f << THRESHOLD_SHIFT) -#define DMA BIT(8) -#define EN_H_DOWN BIT(7) -#define EN_H_UP BIT(6) -#define EN_V_DOWN BIT(5) -#define EN_V_UP BIT(4) -#define ORDERCAMD BIT(3) - -#define IRQ_MASK (EN_V_UP | EN_V_DOWN | EN_H_UP | EN_H_DOWN | \ - EN_NIRQ | EN_FIFO_FULL) - -/* STATUS bit shifts */ -#define HSTATUS BIT(1) -#define VSTATUS BIT(0) - -/* GPIO bit shifts */ -#define CAM_RST BIT(0) - -/* end of OMAP1 Camera Interface registers */ - - -#define SOCAM_BUS_FLAGS (V4L2_MBUS_MASTER | \ - V4L2_MBUS_HSYNC_ACTIVE_HIGH | V4L2_MBUS_VSYNC_ACTIVE_HIGH | \ - V4L2_MBUS_PCLK_SAMPLE_RISING | V4L2_MBUS_PCLK_SAMPLE_FALLING | \ - V4L2_MBUS_DATA_ACTIVE_HIGH) - - -#define FIFO_SIZE ((THRESHOLD_MASK >> THRESHOLD_SHIFT) + 1) -#define FIFO_SHIFT __fls(FIFO_SIZE) - -#define DMA_BURST_SHIFT (1 + OMAP_DMA_DATA_BURST_4) -#define DMA_BURST_SIZE (1 << DMA_BURST_SHIFT) - -#define DMA_ELEMENT_SHIFT OMAP_DMA_DATA_TYPE_S32 -#define DMA_ELEMENT_SIZE (1 << DMA_ELEMENT_SHIFT) - -#define DMA_FRAME_SHIFT_CONTIG (FIFO_SHIFT - 1) -#define DMA_FRAME_SHIFT_SG DMA_BURST_SHIFT - -#define DMA_FRAME_SHIFT(x) ((x) == OMAP1_CAM_DMA_CONTIG ? \ - DMA_FRAME_SHIFT_CONTIG : \ - DMA_FRAME_SHIFT_SG) -#define DMA_FRAME_SIZE(x) (1 << DMA_FRAME_SHIFT(x)) -#define DMA_SYNC OMAP_DMA_SYNC_FRAME -#define THRESHOLD_LEVEL DMA_FRAME_SIZE - - -#define MAX_VIDEO_MEM 4 /* arbitrary video memory limit in MB */ - - -/* - * Structures - */ - -/* buffer for one video frame */ -struct omap1_cam_buf { - struct videobuf_buffer vb; - u32 code; - int inwork; - struct scatterlist *sgbuf; - int sgcount; - int bytes_left; - enum videobuf_state result; -}; - -struct omap1_cam_dev { - struct soc_camera_host soc_host; - struct clk *clk; - - unsigned int irq; - void __iomem *base; - - int dma_ch; - - struct omap1_cam_platform_data *pdata; - struct resource *res; - unsigned long pflags; - unsigned long camexclk; - - struct list_head capture; - - /* lock used to protect videobuf */ - spinlock_t lock; - - /* Pointers to DMA buffers */ - struct omap1_cam_buf *active; - struct omap1_cam_buf *ready; - - enum omap1_cam_vb_mode vb_mode; - int (*mmap_mapper)(struct videobuf_queue *q, - struct videobuf_buffer *buf, - struct vm_area_struct *vma); - - u32 reg_cache[0]; -}; - - -static void cam_write(struct omap1_cam_dev *pcdev, u16 reg, u32 val) -{ - pcdev->reg_cache[reg / sizeof(u32)] = val; - __raw_writel(val, pcdev->base + reg); -} - -static u32 cam_read(struct omap1_cam_dev *pcdev, u16 reg, bool from_cache) -{ - return !from_cache ? __raw_readl(pcdev->base + reg) : - pcdev->reg_cache[reg / sizeof(u32)]; -} - -#define CAM_READ(pcdev, reg) \ - cam_read(pcdev, REG_##reg, false) -#define CAM_WRITE(pcdev, reg, val) \ - cam_write(pcdev, REG_##reg, val) -#define CAM_READ_CACHE(pcdev, reg) \ - cam_read(pcdev, REG_##reg, true) - -/* - * Videobuf operations - */ -static int omap1_videobuf_setup(struct videobuf_queue *vq, unsigned int *count, - unsigned int *size) -{ - struct soc_camera_device *icd = vq->priv_data; - struct soc_camera_host *ici = to_soc_camera_host(icd->parent); - struct omap1_cam_dev *pcdev = ici->priv; - - *size = icd->sizeimage; - - if (!*count || *count < OMAP1_CAMERA_MIN_BUF_COUNT(pcdev->vb_mode)) - *count = OMAP1_CAMERA_MIN_BUF_COUNT(pcdev->vb_mode); - - if (*size * *count > MAX_VIDEO_MEM * 1024 * 1024) - *count = (MAX_VIDEO_MEM * 1024 * 1024) / *size; - - dev_dbg(icd->parent, - "%s: count=%d, size=%d\n", __func__, *count, *size); - - return 0; -} - -static void free_buffer(struct videobuf_queue *vq, struct omap1_cam_buf *buf, - enum omap1_cam_vb_mode vb_mode) -{ - struct videobuf_buffer *vb = &buf->vb; - - BUG_ON(in_interrupt()); - - videobuf_waiton(vq, vb, 0, 0); - - if (vb_mode == OMAP1_CAM_DMA_CONTIG) { - videobuf_dma_contig_free(vq, vb); - } else { - struct soc_camera_device *icd = vq->priv_data; - struct device *dev = icd->parent; - struct videobuf_dmabuf *dma = videobuf_to_dma(vb); - - videobuf_dma_unmap(dev, dma); - videobuf_dma_free(dma); - } - - vb->state = VIDEOBUF_NEEDS_INIT; -} - -static int omap1_videobuf_prepare(struct videobuf_queue *vq, - struct videobuf_buffer *vb, enum v4l2_field field) -{ - struct soc_camera_device *icd = vq->priv_data; - struct omap1_cam_buf *buf = container_of(vb, struct omap1_cam_buf, vb); - struct soc_camera_host *ici = to_soc_camera_host(icd->parent); - struct omap1_cam_dev *pcdev = ici->priv; - int ret; - - WARN_ON(!list_empty(&vb->queue)); - - BUG_ON(NULL == icd->current_fmt); - - buf->inwork = 1; - - if (buf->code != icd->current_fmt->code || vb->field != field || - vb->width != icd->user_width || - vb->height != icd->user_height) { - buf->code = icd->current_fmt->code; - vb->width = icd->user_width; - vb->height = icd->user_height; - vb->field = field; - vb->state = VIDEOBUF_NEEDS_INIT; - } - - vb->size = icd->sizeimage; - - if (vb->baddr && vb->bsize < vb->size) { - ret = -EINVAL; - goto out; - } - - if (vb->state == VIDEOBUF_NEEDS_INIT) { - ret = videobuf_iolock(vq, vb, NULL); - if (ret) - goto fail; - - vb->state = VIDEOBUF_PREPARED; - } - buf->inwork = 0; - - return 0; -fail: - free_buffer(vq, buf, pcdev->vb_mode); -out: - buf->inwork = 0; - return ret; -} - -static void set_dma_dest_params(int dma_ch, struct omap1_cam_buf *buf, - enum omap1_cam_vb_mode vb_mode) -{ - dma_addr_t dma_addr; - unsigned int block_size; - - if (vb_mode == OMAP1_CAM_DMA_CONTIG) { - dma_addr = videobuf_to_dma_contig(&buf->vb); - block_size = buf->vb.size; - } else { - if (WARN_ON(!buf->sgbuf)) { - buf->result = VIDEOBUF_ERROR; - return; - } - dma_addr = sg_dma_address(buf->sgbuf); - if (WARN_ON(!dma_addr)) { - buf->sgbuf = NULL; - buf->result = VIDEOBUF_ERROR; - return; - } - block_size = sg_dma_len(buf->sgbuf); - if (WARN_ON(!block_size)) { - buf->sgbuf = NULL; - buf->result = VIDEOBUF_ERROR; - return; - } - if (unlikely(buf->bytes_left < block_size)) - block_size = buf->bytes_left; - if (WARN_ON(dma_addr & (DMA_FRAME_SIZE(vb_mode) * - DMA_ELEMENT_SIZE - 1))) { - dma_addr = ALIGN(dma_addr, DMA_FRAME_SIZE(vb_mode) * - DMA_ELEMENT_SIZE); - block_size &= ~(DMA_FRAME_SIZE(vb_mode) * - DMA_ELEMENT_SIZE - 1); - } - buf->bytes_left -= block_size; - buf->sgcount++; - } - - omap_set_dma_dest_params(dma_ch, - OMAP_DMA_PORT_EMIFF, OMAP_DMA_AMODE_POST_INC, dma_addr, 0, 0); - omap_set_dma_transfer_params(dma_ch, - OMAP_DMA_DATA_TYPE_S32, DMA_FRAME_SIZE(vb_mode), - block_size >> (DMA_FRAME_SHIFT(vb_mode) + DMA_ELEMENT_SHIFT), - DMA_SYNC, 0, 0); -} - -static struct omap1_cam_buf *prepare_next_vb(struct omap1_cam_dev *pcdev) -{ - struct omap1_cam_buf *buf; - - /* - * If there is already a buffer pointed out by the pcdev->ready, - * (re)use it, otherwise try to fetch and configure a new one. - */ - buf = pcdev->ready; - if (!buf) { - if (list_empty(&pcdev->capture)) - return buf; - buf = list_entry(pcdev->capture.next, - struct omap1_cam_buf, vb.queue); - buf->vb.state = VIDEOBUF_ACTIVE; - pcdev->ready = buf; - list_del_init(&buf->vb.queue); - } - - if (pcdev->vb_mode == OMAP1_CAM_DMA_CONTIG) { - /* - * In CONTIG mode, we can safely enter next buffer parameters - * into the DMA programming register set after the DMA - * has already been activated on the previous buffer - */ - set_dma_dest_params(pcdev->dma_ch, buf, pcdev->vb_mode); - } else { - /* - * In SG mode, the above is not safe since there are probably - * a bunch of sgbufs from previous sglist still pending. - * Instead, mark the sglist fresh for the upcoming - * try_next_sgbuf(). - */ - buf->sgbuf = NULL; - } - - return buf; -} - -static struct scatterlist *try_next_sgbuf(int dma_ch, struct omap1_cam_buf *buf) -{ - struct scatterlist *sgbuf; - - if (likely(buf->sgbuf)) { - /* current sglist is active */ - if (unlikely(!buf->bytes_left)) { - /* indicate sglist complete */ - sgbuf = NULL; - } else { - /* process next sgbuf */ - sgbuf = sg_next(buf->sgbuf); - if (WARN_ON(!sgbuf)) { - buf->result = VIDEOBUF_ERROR; - } else if (WARN_ON(!sg_dma_len(sgbuf))) { - sgbuf = NULL; - buf->result = VIDEOBUF_ERROR; - } - } - buf->sgbuf = sgbuf; - } else { - /* sglist is fresh, initialize it before using */ - struct videobuf_dmabuf *dma = videobuf_to_dma(&buf->vb); - - sgbuf = dma->sglist; - if (!(WARN_ON(!sgbuf))) { - buf->sgbuf = sgbuf; - buf->sgcount = 0; - buf->bytes_left = buf->vb.size; - buf->result = VIDEOBUF_DONE; - } - } - if (sgbuf) - /* - * Put our next sgbuf parameters (address, size) - * into the DMA programming register set. - */ - set_dma_dest_params(dma_ch, buf, OMAP1_CAM_DMA_SG); - - return sgbuf; -} - -static void start_capture(struct omap1_cam_dev *pcdev) -{ - struct omap1_cam_buf *buf = pcdev->active; - u32 ctrlclock = CAM_READ_CACHE(pcdev, CTRLCLOCK); - u32 mode = CAM_READ_CACHE(pcdev, MODE) & ~EN_V_DOWN; - - if (WARN_ON(!buf)) - return; - - /* - * Enable start of frame interrupt, which we will use for activating - * our end of frame watchdog when capture actually starts. - */ - mode |= EN_V_UP; - - if (unlikely(ctrlclock & LCLK_EN)) - /* stop pixel clock before FIFO reset */ - CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock & ~LCLK_EN); - /* reset FIFO */ - CAM_WRITE(pcdev, MODE, mode | RAZ_FIFO); - - omap_start_dma(pcdev->dma_ch); - - if (pcdev->vb_mode == OMAP1_CAM_DMA_SG) { - /* - * In SG mode, it's a good moment for fetching next sgbuf - * from the current sglist and, if available, already putting - * its parameters into the DMA programming register set. - */ - try_next_sgbuf(pcdev->dma_ch, buf); - } - - /* (re)enable pixel clock */ - CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock | LCLK_EN); - /* release FIFO reset */ - CAM_WRITE(pcdev, MODE, mode); -} - -static void suspend_capture(struct omap1_cam_dev *pcdev) -{ - u32 ctrlclock = CAM_READ_CACHE(pcdev, CTRLCLOCK); - - CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock & ~LCLK_EN); - omap_stop_dma(pcdev->dma_ch); -} - -static void disable_capture(struct omap1_cam_dev *pcdev) -{ - u32 mode = CAM_READ_CACHE(pcdev, MODE); - - CAM_WRITE(pcdev, MODE, mode & ~(IRQ_MASK | DMA)); -} - -static void omap1_videobuf_queue(struct videobuf_queue *vq, - struct videobuf_buffer *vb) -{ - struct soc_camera_device *icd = vq->priv_data; - struct soc_camera_host *ici = to_soc_camera_host(icd->parent); - struct omap1_cam_dev *pcdev = ici->priv; - struct omap1_cam_buf *buf; - u32 mode; - - list_add_tail(&vb->queue, &pcdev->capture); - vb->state = VIDEOBUF_QUEUED; - - if (pcdev->active) { - /* - * Capture in progress, so don't touch pcdev->ready even if - * empty. Since the transfer of the DMA programming register set - * content to the DMA working register set is done automatically - * by the DMA hardware, this can pretty well happen while we - * are keeping the lock here. Leave fetching it from the queue - * to be done when a next DMA interrupt occures instead. - */ - return; - } - - WARN_ON(pcdev->ready); - - buf = prepare_next_vb(pcdev); - if (WARN_ON(!buf)) - return; - - pcdev->active = buf; - pcdev->ready = NULL; - - dev_dbg(icd->parent, - "%s: capture not active, setup FIFO, start DMA\n", __func__); - mode = CAM_READ_CACHE(pcdev, MODE) & ~THRESHOLD_MASK; - mode |= THRESHOLD_LEVEL(pcdev->vb_mode) << THRESHOLD_SHIFT; - CAM_WRITE(pcdev, MODE, mode | EN_FIFO_FULL | DMA); - - if (pcdev->vb_mode == OMAP1_CAM_DMA_SG) { - /* - * In SG mode, the above prepare_next_vb() didn't actually - * put anything into the DMA programming register set, - * so we have to do it now, before activating DMA. - */ - try_next_sgbuf(pcdev->dma_ch, buf); - } - - start_capture(pcdev); -} - -static void omap1_videobuf_release(struct videobuf_queue *vq, - struct videobuf_buffer *vb) -{ - struct omap1_cam_buf *buf = - container_of(vb, struct omap1_cam_buf, vb); - struct soc_camera_device *icd = vq->priv_data; - struct device *dev = icd->parent; - struct soc_camera_host *ici = to_soc_camera_host(dev); - struct omap1_cam_dev *pcdev = ici->priv; - - switch (vb->state) { - case VIDEOBUF_DONE: - dev_dbg(dev, "%s (done)\n", __func__); - break; - case VIDEOBUF_ACTIVE: - dev_dbg(dev, "%s (active)\n", __func__); - break; - case VIDEOBUF_QUEUED: - dev_dbg(dev, "%s (queued)\n", __func__); - break; - case VIDEOBUF_PREPARED: - dev_dbg(dev, "%s (prepared)\n", __func__); - break; - default: - dev_dbg(dev, "%s (unknown %d)\n", __func__, vb->state); - break; - } - - free_buffer(vq, buf, pcdev->vb_mode); -} - -static void videobuf_done(struct omap1_cam_dev *pcdev, - enum videobuf_state result) -{ - struct omap1_cam_buf *buf = pcdev->active; - struct videobuf_buffer *vb; - struct device *dev = pcdev->soc_host.icd->parent; - - if (WARN_ON(!buf)) { - suspend_capture(pcdev); - disable_capture(pcdev); - return; - } - - if (result == VIDEOBUF_ERROR) - suspend_capture(pcdev); - - vb = &buf->vb; - if (waitqueue_active(&vb->done)) { - if (!pcdev->ready && result != VIDEOBUF_ERROR) { - /* - * No next buffer has been entered into the DMA - * programming register set on time (could be done only - * while the previous DMA interurpt was processed, not - * later), so the last DMA block, be it a whole buffer - * if in CONTIG or its last sgbuf if in SG mode, is - * about to be reused by the just autoreinitialized DMA - * engine, and overwritten with next frame data. Best we - * can do is stopping the capture as soon as possible, - * hopefully before the next frame start. - */ - suspend_capture(pcdev); - } - vb->state = result; - v4l2_get_timestamp(&vb->ts); - if (result != VIDEOBUF_ERROR) - vb->field_count++; - wake_up(&vb->done); - - /* shift in next buffer */ - buf = pcdev->ready; - pcdev->active = buf; - pcdev->ready = NULL; - - if (!buf) { - /* - * No next buffer was ready on time (see above), so - * indicate error condition to force capture restart or - * stop, depending on next buffer already queued or not. - */ - result = VIDEOBUF_ERROR; - prepare_next_vb(pcdev); - - buf = pcdev->ready; - pcdev->active = buf; - pcdev->ready = NULL; - } - } else if (pcdev->ready) { - /* - * In both CONTIG and SG mode, the DMA engine has possibly - * been already autoreinitialized with the preprogrammed - * pcdev->ready buffer. We can either accept this fact - * and just swap the buffers, or provoke an error condition - * and restart capture. The former seems less intrusive. - */ - dev_dbg(dev, "%s: nobody waiting on videobuf, swap with next\n", - __func__); - pcdev->active = pcdev->ready; - - if (pcdev->vb_mode == OMAP1_CAM_DMA_SG) { - /* - * In SG mode, we have to make sure that the buffer we - * are putting back into the pcdev->ready is marked - * fresh. - */ - buf->sgbuf = NULL; - } - pcdev->ready = buf; - - buf = pcdev->active; - } else { - /* - * No next buffer has been entered into - * the DMA programming register set on time. - */ - if (pcdev->vb_mode == OMAP1_CAM_DMA_CONTIG) { - /* - * In CONTIG mode, the DMA engine has already been - * reinitialized with the current buffer. Best we can do - * is not touching it. - */ - dev_dbg(dev, - "%s: nobody waiting on videobuf, reuse it\n", - __func__); - } else { - /* - * In SG mode, the DMA engine has just been - * autoreinitialized with the last sgbuf from the - * current list. Restart capture in order to transfer - * next frame start into the first sgbuf, not the last - * one. - */ - if (result != VIDEOBUF_ERROR) { - suspend_capture(pcdev); - result = VIDEOBUF_ERROR; - } - } - } - - if (!buf) { - dev_dbg(dev, "%s: no more videobufs, stop capture\n", __func__); - disable_capture(pcdev); - return; - } - - if (pcdev->vb_mode == OMAP1_CAM_DMA_CONTIG) { - /* - * In CONTIG mode, the current buffer parameters had already - * been entered into the DMA programming register set while the - * buffer was fetched with prepare_next_vb(), they may have also - * been transferred into the runtime set and already active if - * the DMA still running. - */ - } else { - /* In SG mode, extra steps are required */ - if (result == VIDEOBUF_ERROR) - /* make sure we (re)use sglist from start on error */ - buf->sgbuf = NULL; - - /* - * In any case, enter the next sgbuf parameters into the DMA - * programming register set. They will be used either during - * nearest DMA autoreinitialization or, in case of an error, - * on DMA startup below. - */ - try_next_sgbuf(pcdev->dma_ch, buf); - } - - if (result == VIDEOBUF_ERROR) { - dev_dbg(dev, "%s: videobuf error; reset FIFO, restart DMA\n", - __func__); - start_capture(pcdev); - /* - * In SG mode, the above also resulted in the next sgbuf - * parameters being entered into the DMA programming register - * set, making them ready for next DMA autoreinitialization. - */ - } - - /* - * Finally, try fetching next buffer. - * In CONTIG mode, it will also enter it into the DMA programming - * register set, making it ready for next DMA autoreinitialization. - */ - prepare_next_vb(pcdev); -} - -static void dma_isr(int channel, unsigned short status, void *data) -{ - struct omap1_cam_dev *pcdev = data; - struct omap1_cam_buf *buf = pcdev->active; - unsigned long flags; - - spin_lock_irqsave(&pcdev->lock, flags); - - if (WARN_ON(!buf)) { - suspend_capture(pcdev); - disable_capture(pcdev); - goto out; - } - - if (pcdev->vb_mode == OMAP1_CAM_DMA_CONTIG) { - /* - * In CONTIG mode, assume we have just managed to collect the - * whole frame, hopefully before our end of frame watchdog is - * triggered. Then, all we have to do is disabling the watchdog - * for this frame, and calling videobuf_done() with success - * indicated. - */ - CAM_WRITE(pcdev, MODE, - CAM_READ_CACHE(pcdev, MODE) & ~EN_V_DOWN); - videobuf_done(pcdev, VIDEOBUF_DONE); - } else { - /* - * In SG mode, we have to process every sgbuf from the current - * sglist, one after another. - */ - if (buf->sgbuf) { - /* - * Current sglist not completed yet, try fetching next - * sgbuf, hopefully putting it into the DMA programming - * register set, making it ready for next DMA - * autoreinitialization. - */ - try_next_sgbuf(pcdev->dma_ch, buf); - if (buf->sgbuf) - goto out; - - /* - * No more sgbufs left in the current sglist. This - * doesn't mean that the whole videobuffer is already - * complete, but only that the last sgbuf from the - * current sglist is about to be filled. It will be - * ready on next DMA interrupt, signalled with the - * buf->sgbuf set back to NULL. - */ - if (buf->result != VIDEOBUF_ERROR) { - /* - * Video frame collected without errors so far, - * we can prepare for collecting a next one - * as soon as DMA gets autoreinitialized - * after the current (last) sgbuf is completed. - */ - buf = prepare_next_vb(pcdev); - if (!buf) - goto out; - - try_next_sgbuf(pcdev->dma_ch, buf); - goto out; - } - } - /* end of videobuf */ - videobuf_done(pcdev, buf->result); - } - -out: - spin_unlock_irqrestore(&pcdev->lock, flags); -} - -static irqreturn_t cam_isr(int irq, void *data) -{ - struct omap1_cam_dev *pcdev = data; - struct device *dev = pcdev->soc_host.icd->parent; - struct omap1_cam_buf *buf = pcdev->active; - u32 it_status; - unsigned long flags; - - it_status = CAM_READ(pcdev, IT_STATUS); - if (!it_status) - return IRQ_NONE; - - spin_lock_irqsave(&pcdev->lock, flags); - - if (WARN_ON(!buf)) { - dev_warn(dev, "%s: unhandled camera interrupt, status == %#x\n", - __func__, it_status); - suspend_capture(pcdev); - disable_capture(pcdev); - goto out; - } - - if (unlikely(it_status & FIFO_FULL)) { - dev_warn(dev, "%s: FIFO overflow\n", __func__); - - } else if (it_status & V_DOWN) { - /* end of video frame watchdog */ - if (pcdev->vb_mode == OMAP1_CAM_DMA_CONTIG) { - /* - * In CONTIG mode, the watchdog is disabled with - * successful DMA end of block interrupt, and reenabled - * on next frame start. If we get here, there is nothing - * to check, we must be out of sync. - */ - } else { - if (buf->sgcount == 2) { - /* - * If exactly 2 sgbufs from the next sglist have - * been programmed into the DMA engine (the - * first one already transferred into the DMA - * runtime register set, the second one still - * in the programming set), then we are in sync. - */ - goto out; - } - } - dev_notice(dev, "%s: unexpected end of video frame\n", - __func__); - - } else if (it_status & V_UP) { - u32 mode; - - if (pcdev->vb_mode == OMAP1_CAM_DMA_CONTIG) { - /* - * In CONTIG mode, we need this interrupt every frame - * in oredr to reenable our end of frame watchdog. - */ - mode = CAM_READ_CACHE(pcdev, MODE); - } else { - /* - * In SG mode, the below enabled end of frame watchdog - * is kept on permanently, so we can turn this one shot - * setup off. - */ - mode = CAM_READ_CACHE(pcdev, MODE) & ~EN_V_UP; - } - - if (!(mode & EN_V_DOWN)) { - /* (re)enable end of frame watchdog interrupt */ - mode |= EN_V_DOWN; - } - CAM_WRITE(pcdev, MODE, mode); - goto out; - - } else { - dev_warn(dev, "%s: unhandled camera interrupt, status == %#x\n", - __func__, it_status); - goto out; - } - - videobuf_done(pcdev, VIDEOBUF_ERROR); -out: - spin_unlock_irqrestore(&pcdev->lock, flags); - return IRQ_HANDLED; -} - -static struct videobuf_queue_ops omap1_videobuf_ops = { - .buf_setup = omap1_videobuf_setup, - .buf_prepare = omap1_videobuf_prepare, - .buf_queue = omap1_videobuf_queue, - .buf_release = omap1_videobuf_release, -}; - - -/* - * SOC Camera host operations - */ - -static void sensor_reset(struct omap1_cam_dev *pcdev, bool reset) -{ - /* apply/release camera sensor reset if requested by platform data */ - if (pcdev->pflags & OMAP1_CAMERA_RST_HIGH) - CAM_WRITE(pcdev, GPIO, reset); - else if (pcdev->pflags & OMAP1_CAMERA_RST_LOW) - CAM_WRITE(pcdev, GPIO, !reset); -} - -static int omap1_cam_add_device(struct soc_camera_device *icd) -{ - dev_dbg(icd->parent, "OMAP1 Camera driver attached to camera %d\n", - icd->devnum); - - return 0; -} - -static void omap1_cam_remove_device(struct soc_camera_device *icd) -{ - dev_dbg(icd->parent, - "OMAP1 Camera driver detached from camera %d\n", icd->devnum); -} - -/* - * The following two functions absolutely depend on the fact, that - * there can be only one camera on OMAP1 camera sensor interface - */ -static int omap1_cam_clock_start(struct soc_camera_host *ici) -{ - struct omap1_cam_dev *pcdev = ici->priv; - u32 ctrlclock; - - clk_enable(pcdev->clk); - - /* setup sensor clock */ - ctrlclock = CAM_READ(pcdev, CTRLCLOCK); - ctrlclock &= ~(CAMEXCLK_EN | MCLK_EN | DPLL_EN); - CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock); - - ctrlclock &= ~FOSCMOD_MASK; - switch (pcdev->camexclk) { - case 6000000: - ctrlclock |= CAMEXCLK_EN | FOSCMOD_6MHz; - break; - case 8000000: - ctrlclock |= CAMEXCLK_EN | FOSCMOD_8MHz | DPLL_EN; - break; - case 9600000: - ctrlclock |= CAMEXCLK_EN | FOSCMOD_9_6MHz | DPLL_EN; - break; - case 12000000: - ctrlclock |= CAMEXCLK_EN | FOSCMOD_12MHz; - break; - case 24000000: - ctrlclock |= CAMEXCLK_EN | FOSCMOD_24MHz | DPLL_EN; - default: - break; - } - CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock & ~DPLL_EN); - - /* enable internal clock */ - ctrlclock |= MCLK_EN; - CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock); - - sensor_reset(pcdev, false); - - return 0; -} - -static void omap1_cam_clock_stop(struct soc_camera_host *ici) -{ - struct omap1_cam_dev *pcdev = ici->priv; - u32 ctrlclock; - - suspend_capture(pcdev); - disable_capture(pcdev); - - sensor_reset(pcdev, true); - - /* disable and release system clocks */ - ctrlclock = CAM_READ_CACHE(pcdev, CTRLCLOCK); - ctrlclock &= ~(MCLK_EN | DPLL_EN | CAMEXCLK_EN); - CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock); - - ctrlclock = (ctrlclock & ~FOSCMOD_MASK) | FOSCMOD_12MHz; - CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock); - CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock | MCLK_EN); - - CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock & ~MCLK_EN); - - clk_disable(pcdev->clk); -} - -/* Duplicate standard formats based on host capability of byte swapping */ -static const struct soc_mbus_lookup omap1_cam_formats[] = { -{ - .code = MEDIA_BUS_FMT_UYVY8_2X8, - .fmt = { - .fourcc = V4L2_PIX_FMT_YUYV, - .name = "YUYV", - .bits_per_sample = 8, - .packing = SOC_MBUS_PACKING_2X8_PADHI, - .order = SOC_MBUS_ORDER_BE, - .layout = SOC_MBUS_LAYOUT_PACKED, - }, -}, { - .code = MEDIA_BUS_FMT_VYUY8_2X8, - .fmt = { - .fourcc = V4L2_PIX_FMT_YVYU, - .name = "YVYU", - .bits_per_sample = 8, - .packing = SOC_MBUS_PACKING_2X8_PADHI, - .order = SOC_MBUS_ORDER_BE, - .layout = SOC_MBUS_LAYOUT_PACKED, - }, -}, { - .code = MEDIA_BUS_FMT_YUYV8_2X8, - .fmt = { - .fourcc = V4L2_PIX_FMT_UYVY, - .name = "UYVY", - .bits_per_sample = 8, - .packing = SOC_MBUS_PACKING_2X8_PADHI, - .order = SOC_MBUS_ORDER_BE, - .layout = SOC_MBUS_LAYOUT_PACKED, - }, -}, { - .code = MEDIA_BUS_FMT_YVYU8_2X8, - .fmt = { - .fourcc = V4L2_PIX_FMT_VYUY, - .name = "VYUY", - .bits_per_sample = 8, - .packing = SOC_MBUS_PACKING_2X8_PADHI, - .order = SOC_MBUS_ORDER_BE, - .layout = SOC_MBUS_LAYOUT_PACKED, - }, -}, { - .code = MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE, - .fmt = { - .fourcc = V4L2_PIX_FMT_RGB555, - .name = "RGB555", - .bits_per_sample = 8, - .packing = SOC_MBUS_PACKING_2X8_PADHI, - .order = SOC_MBUS_ORDER_BE, - .layout = SOC_MBUS_LAYOUT_PACKED, - }, -}, { - .code = MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE, - .fmt = { - .fourcc = V4L2_PIX_FMT_RGB555X, - .name = "RGB555X", - .bits_per_sample = 8, - .packing = SOC_MBUS_PACKING_2X8_PADHI, - .order = SOC_MBUS_ORDER_BE, - .layout = SOC_MBUS_LAYOUT_PACKED, - }, -}, { - .code = MEDIA_BUS_FMT_RGB565_2X8_BE, - .fmt = { - .fourcc = V4L2_PIX_FMT_RGB565, - .name = "RGB565", - .bits_per_sample = 8, - .packing = SOC_MBUS_PACKING_2X8_PADHI, - .order = SOC_MBUS_ORDER_BE, - .layout = SOC_MBUS_LAYOUT_PACKED, - }, -}, { - .code = MEDIA_BUS_FMT_RGB565_2X8_LE, - .fmt = { - .fourcc = V4L2_PIX_FMT_RGB565X, - .name = "RGB565X", - .bits_per_sample = 8, - .packing = SOC_MBUS_PACKING_2X8_PADHI, - .order = SOC_MBUS_ORDER_BE, - .layout = SOC_MBUS_LAYOUT_PACKED, - }, -}, -}; - -static int omap1_cam_get_formats(struct soc_camera_device *icd, - unsigned int idx, struct soc_camera_format_xlate *xlate) -{ - struct v4l2_subdev *sd = soc_camera_to_subdev(icd); - struct device *dev = icd->parent; - int formats = 0, ret; - struct v4l2_subdev_mbus_code_enum code = { - .which = V4L2_SUBDEV_FORMAT_ACTIVE, - .index = idx, - }; - const struct soc_mbus_pixelfmt *fmt; - - ret = v4l2_subdev_call(sd, pad, enum_mbus_code, NULL, &code); - if (ret < 0) - /* No more formats */ - return 0; - - fmt = soc_mbus_get_fmtdesc(code.code); - if (!fmt) { - dev_warn(dev, "%s: unsupported format code #%d: %d\n", __func__, - idx, code.code); - return 0; - } - - /* Check support for the requested bits-per-sample */ - if (fmt->bits_per_sample != 8) - return 0; - - switch (code.code) { - case MEDIA_BUS_FMT_YUYV8_2X8: - case MEDIA_BUS_FMT_YVYU8_2X8: - case MEDIA_BUS_FMT_UYVY8_2X8: - case MEDIA_BUS_FMT_VYUY8_2X8: - case MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE: - case MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE: - case MEDIA_BUS_FMT_RGB565_2X8_BE: - case MEDIA_BUS_FMT_RGB565_2X8_LE: - formats++; - if (xlate) { - xlate->host_fmt = soc_mbus_find_fmtdesc(code.code, - omap1_cam_formats, - ARRAY_SIZE(omap1_cam_formats)); - xlate->code = code.code; - xlate++; - dev_dbg(dev, - "%s: providing format %s as byte swapped code #%d\n", - __func__, xlate->host_fmt->name, code.code); - } - default: - if (xlate) - dev_dbg(dev, - "%s: providing format %s in pass-through mode\n", - __func__, fmt->name); - } - formats++; - if (xlate) { - xlate->host_fmt = fmt; - xlate->code = code.code; - xlate++; - } - - return formats; -} - -static bool is_dma_aligned(s32 bytes_per_line, unsigned int height, - enum omap1_cam_vb_mode vb_mode) -{ - int size = bytes_per_line * height; - - return IS_ALIGNED(bytes_per_line, DMA_ELEMENT_SIZE) && - IS_ALIGNED(size, DMA_FRAME_SIZE(vb_mode) * DMA_ELEMENT_SIZE); -} - -static int dma_align(int *width, int *height, - const struct soc_mbus_pixelfmt *fmt, - enum omap1_cam_vb_mode vb_mode, bool enlarge) -{ - s32 bytes_per_line = soc_mbus_bytes_per_line(*width, fmt); - - if (bytes_per_line < 0) - return bytes_per_line; - - if (!is_dma_aligned(bytes_per_line, *height, vb_mode)) { - unsigned int pxalign = __fls(bytes_per_line / *width); - unsigned int salign = DMA_FRAME_SHIFT(vb_mode) + - DMA_ELEMENT_SHIFT - pxalign; - unsigned int incr = enlarge << salign; - - v4l_bound_align_image(width, 1, *width + incr, 0, - height, 1, *height + incr, 0, salign); - return 0; - } - return 1; -} - -#define subdev_call_with_sense(pcdev, dev, icd, sd, op, function, args...) \ -({ \ - struct soc_camera_sense sense = { \ - .master_clock = pcdev->camexclk, \ - .pixel_clock_max = 0, \ - }; \ - int __ret; \ - \ - if (pcdev->pdata) \ - sense.pixel_clock_max = pcdev->pdata->lclk_khz_max * 1000; \ - icd->sense = &sense; \ - __ret = v4l2_subdev_call(sd, op, function, ##args); \ - icd->sense = NULL; \ - \ - if (sense.flags & SOCAM_SENSE_PCLK_CHANGED) { \ - if (sense.pixel_clock > sense.pixel_clock_max) { \ - dev_err(dev, \ - "%s: pixel clock %lu set by the camera too high!\n", \ - __func__, sense.pixel_clock); \ - __ret = -EINVAL; \ - } \ - } \ - __ret; \ -}) - -static int set_format(struct omap1_cam_dev *pcdev, struct device *dev, - struct soc_camera_device *icd, struct v4l2_subdev *sd, - struct v4l2_subdev_format *format, - const struct soc_camera_format_xlate *xlate) -{ - s32 bytes_per_line; - struct v4l2_mbus_framefmt *mf = &format->format; - int ret = subdev_call_with_sense(pcdev, dev, icd, sd, pad, set_fmt, NULL, format); - - if (ret < 0) { - dev_err(dev, "%s: set_fmt failed\n", __func__); - return ret; - } - - if (mf->code != xlate->code) { - dev_err(dev, "%s: unexpected pixel code change\n", __func__); - return -EINVAL; - } - - bytes_per_line = soc_mbus_bytes_per_line(mf->width, xlate->host_fmt); - if (bytes_per_line < 0) { - dev_err(dev, "%s: soc_mbus_bytes_per_line() failed\n", - __func__); - return bytes_per_line; - } - - if (!is_dma_aligned(bytes_per_line, mf->height, pcdev->vb_mode)) { - dev_err(dev, "%s: resulting geometry %ux%u not DMA aligned\n", - __func__, mf->width, mf->height); - return -EINVAL; - } - return 0; -} - -static int omap1_cam_set_crop(struct soc_camera_device *icd, - const struct v4l2_crop *crop) -{ - const struct v4l2_rect *rect = &crop->c; - const struct soc_camera_format_xlate *xlate = icd->current_fmt; - struct v4l2_subdev *sd = soc_camera_to_subdev(icd); - struct device *dev = icd->parent; - struct soc_camera_host *ici = to_soc_camera_host(dev); - struct omap1_cam_dev *pcdev = ici->priv; - struct v4l2_subdev_format fmt = { - .which = V4L2_SUBDEV_FORMAT_ACTIVE, - }; - struct v4l2_mbus_framefmt *mf = &fmt.format; - int ret; - - ret = subdev_call_with_sense(pcdev, dev, icd, sd, video, s_crop, crop); - if (ret < 0) { - dev_warn(dev, "%s: failed to crop to %ux%u@%u:%u\n", __func__, - rect->width, rect->height, rect->left, rect->top); - return ret; - } - - ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &fmt); - if (ret < 0) { - dev_warn(dev, "%s: failed to fetch current format\n", __func__); - return ret; - } - - ret = dma_align(&mf->width, &mf->height, xlate->host_fmt, pcdev->vb_mode, - false); - if (ret < 0) { - dev_err(dev, "%s: failed to align %ux%u %s with DMA\n", - __func__, mf->width, mf->height, - xlate->host_fmt->name); - return ret; - } - - if (!ret) { - /* sensor returned geometry not DMA aligned, trying to fix */ - ret = set_format(pcdev, dev, icd, sd, &fmt, xlate); - if (ret < 0) { - dev_err(dev, "%s: failed to set format\n", __func__); - return ret; - } - } - - icd->user_width = mf->width; - icd->user_height = mf->height; - - return 0; -} - -static int omap1_cam_set_fmt(struct soc_camera_device *icd, - struct v4l2_format *f) -{ - struct v4l2_subdev *sd = soc_camera_to_subdev(icd); - const struct soc_camera_format_xlate *xlate; - struct device *dev = icd->parent; - struct soc_camera_host *ici = to_soc_camera_host(dev); - struct omap1_cam_dev *pcdev = ici->priv; - struct v4l2_pix_format *pix = &f->fmt.pix; - struct v4l2_subdev_format format = { - .which = V4L2_SUBDEV_FORMAT_ACTIVE, - }; - struct v4l2_mbus_framefmt *mf = &format.format; - int ret; - - xlate = soc_camera_xlate_by_fourcc(icd, pix->pixelformat); - if (!xlate) { - dev_warn(dev, "%s: format %#x not found\n", __func__, - pix->pixelformat); - return -EINVAL; - } - - mf->width = pix->width; - mf->height = pix->height; - mf->field = pix->field; - mf->colorspace = pix->colorspace; - mf->code = xlate->code; - - ret = dma_align(&mf->width, &mf->height, xlate->host_fmt, pcdev->vb_mode, - true); - if (ret < 0) { - dev_err(dev, "%s: failed to align %ux%u %s with DMA\n", - __func__, pix->width, pix->height, - xlate->host_fmt->name); - return ret; - } - - ret = set_format(pcdev, dev, icd, sd, &format, xlate); - if (ret < 0) { - dev_err(dev, "%s: failed to set format\n", __func__); - return ret; - } - - pix->width = mf->width; - pix->height = mf->height; - pix->field = mf->field; - pix->colorspace = mf->colorspace; - icd->current_fmt = xlate; - - return 0; -} - -static int omap1_cam_try_fmt(struct soc_camera_device *icd, - struct v4l2_format *f) -{ - struct v4l2_subdev *sd = soc_camera_to_subdev(icd); - const struct soc_camera_format_xlate *xlate; - struct v4l2_pix_format *pix = &f->fmt.pix; - struct v4l2_subdev_pad_config pad_cfg; - struct v4l2_subdev_format format = { - .which = V4L2_SUBDEV_FORMAT_TRY, - }; - struct v4l2_mbus_framefmt *mf = &format.format; - int ret; - /* TODO: limit to mx1 hardware capabilities */ - - xlate = soc_camera_xlate_by_fourcc(icd, pix->pixelformat); - if (!xlate) { - dev_warn(icd->parent, "Format %#x not found\n", - pix->pixelformat); - return -EINVAL; - } - - mf->width = pix->width; - mf->height = pix->height; - mf->field = pix->field; - mf->colorspace = pix->colorspace; - mf->code = xlate->code; - - /* limit to sensor capabilities */ - ret = v4l2_subdev_call(sd, pad, set_fmt, &pad_cfg, &format); - if (ret < 0) - return ret; - - pix->width = mf->width; - pix->height = mf->height; - pix->field = mf->field; - pix->colorspace = mf->colorspace; - - return 0; -} - -static bool sg_mode; - -/* - * Local mmap_mapper wrapper, - * used for detecting videobuf-dma-contig buffer allocation failures - * and switching to videobuf-dma-sg automatically for future attempts. - */ -static int omap1_cam_mmap_mapper(struct videobuf_queue *q, - struct videobuf_buffer *buf, - struct vm_area_struct *vma) -{ - struct soc_camera_device *icd = q->priv_data; - struct soc_camera_host *ici = to_soc_camera_host(icd->parent); - struct omap1_cam_dev *pcdev = ici->priv; - int ret; - - ret = pcdev->mmap_mapper(q, buf, vma); - - if (ret == -ENOMEM) - sg_mode = true; - - return ret; -} - -static void omap1_cam_init_videobuf(struct videobuf_queue *q, - struct soc_camera_device *icd) -{ - struct soc_camera_host *ici = to_soc_camera_host(icd->parent); - struct omap1_cam_dev *pcdev = ici->priv; - - if (!sg_mode) - videobuf_queue_dma_contig_init(q, &omap1_videobuf_ops, - icd->parent, &pcdev->lock, - V4L2_BUF_TYPE_VIDEO_CAPTURE, V4L2_FIELD_NONE, - sizeof(struct omap1_cam_buf), icd, &ici->host_lock); - else - videobuf_queue_sg_init(q, &omap1_videobuf_ops, - icd->parent, &pcdev->lock, - V4L2_BUF_TYPE_VIDEO_CAPTURE, V4L2_FIELD_NONE, - sizeof(struct omap1_cam_buf), icd, &ici->host_lock); - - /* use videobuf mode (auto)selected with the module parameter */ - pcdev->vb_mode = sg_mode ? OMAP1_CAM_DMA_SG : OMAP1_CAM_DMA_CONTIG; - - /* - * Ensure we substitute the videobuf-dma-contig version of the - * mmap_mapper() callback with our own wrapper, used for switching - * automatically to videobuf-dma-sg on buffer allocation failure. - */ - if (!sg_mode && q->int_ops->mmap_mapper != omap1_cam_mmap_mapper) { - pcdev->mmap_mapper = q->int_ops->mmap_mapper; - q->int_ops->mmap_mapper = omap1_cam_mmap_mapper; - } -} - -static int omap1_cam_reqbufs(struct soc_camera_device *icd, - struct v4l2_requestbuffers *p) -{ - int i; - - /* - * This is for locking debugging only. I removed spinlocks and now I - * check whether .prepare is ever called on a linked buffer, or whether - * a dma IRQ can occur for an in-work or unlinked buffer. Until now - * it hadn't triggered - */ - for (i = 0; i < p->count; i++) { - struct omap1_cam_buf *buf = container_of(icd->vb_vidq.bufs[i], - struct omap1_cam_buf, vb); - buf->inwork = 0; - INIT_LIST_HEAD(&buf->vb.queue); - } - - return 0; -} - -static int omap1_cam_querycap(struct soc_camera_host *ici, - struct v4l2_capability *cap) -{ - /* cap->name is set by the friendly caller:-> */ - strlcpy(cap->card, "OMAP1 Camera", sizeof(cap->card)); - cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; - cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; - - return 0; -} - -static int omap1_cam_set_bus_param(struct soc_camera_device *icd) -{ - struct v4l2_subdev *sd = soc_camera_to_subdev(icd); - struct device *dev = icd->parent; - struct soc_camera_host *ici = to_soc_camera_host(dev); - struct omap1_cam_dev *pcdev = ici->priv; - u32 pixfmt = icd->current_fmt->host_fmt->fourcc; - const struct soc_camera_format_xlate *xlate; - const struct soc_mbus_pixelfmt *fmt; - struct v4l2_mbus_config cfg = {.type = V4L2_MBUS_PARALLEL,}; - unsigned long common_flags; - u32 ctrlclock, mode; - int ret; - - ret = v4l2_subdev_call(sd, video, g_mbus_config, &cfg); - if (!ret) { - common_flags = soc_mbus_config_compatible(&cfg, SOCAM_BUS_FLAGS); - if (!common_flags) { - dev_warn(dev, - "Flags incompatible: camera 0x%x, host 0x%x\n", - cfg.flags, SOCAM_BUS_FLAGS); - return -EINVAL; - } - } else if (ret != -ENOIOCTLCMD) { - return ret; - } else { - common_flags = SOCAM_BUS_FLAGS; - } - - /* Make choices, possibly based on platform configuration */ - if ((common_flags & V4L2_MBUS_PCLK_SAMPLE_RISING) && - (common_flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)) { - if (!pcdev->pdata || - pcdev->pdata->flags & OMAP1_CAMERA_LCLK_RISING) - common_flags &= ~V4L2_MBUS_PCLK_SAMPLE_FALLING; - else - common_flags &= ~V4L2_MBUS_PCLK_SAMPLE_RISING; - } - - cfg.flags = common_flags; - ret = v4l2_subdev_call(sd, video, s_mbus_config, &cfg); - if (ret < 0 && ret != -ENOIOCTLCMD) { - dev_dbg(dev, "camera s_mbus_config(0x%lx) returned %d\n", - common_flags, ret); - return ret; - } - - ctrlclock = CAM_READ_CACHE(pcdev, CTRLCLOCK); - if (ctrlclock & LCLK_EN) - CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock & ~LCLK_EN); - - if (common_flags & V4L2_MBUS_PCLK_SAMPLE_RISING) { - dev_dbg(dev, "CTRLCLOCK_REG |= POLCLK\n"); - ctrlclock |= POLCLK; - } else { - dev_dbg(dev, "CTRLCLOCK_REG &= ~POLCLK\n"); - ctrlclock &= ~POLCLK; - } - CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock & ~LCLK_EN); - - if (ctrlclock & LCLK_EN) - CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock); - - /* select bus endianness */ - xlate = soc_camera_xlate_by_fourcc(icd, pixfmt); - fmt = xlate->host_fmt; - - mode = CAM_READ(pcdev, MODE) & ~(RAZ_FIFO | IRQ_MASK | DMA); - if (fmt->order == SOC_MBUS_ORDER_LE) { - dev_dbg(dev, "MODE_REG &= ~ORDERCAMD\n"); - CAM_WRITE(pcdev, MODE, mode & ~ORDERCAMD); - } else { - dev_dbg(dev, "MODE_REG |= ORDERCAMD\n"); - CAM_WRITE(pcdev, MODE, mode | ORDERCAMD); - } - - return 0; -} - -static unsigned int omap1_cam_poll(struct file *file, poll_table *pt) -{ - struct soc_camera_device *icd = file->private_data; - struct omap1_cam_buf *buf; - - buf = list_entry(icd->vb_vidq.stream.next, struct omap1_cam_buf, - vb.stream); - - poll_wait(file, &buf->vb.done, pt); - - if (buf->vb.state == VIDEOBUF_DONE || - buf->vb.state == VIDEOBUF_ERROR) - return POLLIN | POLLRDNORM; - - return 0; -} - -static struct soc_camera_host_ops omap1_host_ops = { - .owner = THIS_MODULE, - .add = omap1_cam_add_device, - .remove = omap1_cam_remove_device, - .clock_start = omap1_cam_clock_start, - .clock_stop = omap1_cam_clock_stop, - .get_formats = omap1_cam_get_formats, - .set_crop = omap1_cam_set_crop, - .set_fmt = omap1_cam_set_fmt, - .try_fmt = omap1_cam_try_fmt, - .init_videobuf = omap1_cam_init_videobuf, - .reqbufs = omap1_cam_reqbufs, - .querycap = omap1_cam_querycap, - .set_bus_param = omap1_cam_set_bus_param, - .poll = omap1_cam_poll, -}; - -static int omap1_cam_probe(struct platform_device *pdev) -{ - struct omap1_cam_dev *pcdev; - struct resource *res; - struct clk *clk; - void __iomem *base; - unsigned int irq; - int err = 0; - - irq = platform_get_irq(pdev, 0); - if ((int)irq <= 0) { - err = -ENODEV; - goto exit; - } - - clk = devm_clk_get(&pdev->dev, "armper_ck"); - if (IS_ERR(clk)) - return PTR_ERR(clk); - - pcdev = devm_kzalloc(&pdev->dev, sizeof(*pcdev) + resource_size(res), - GFP_KERNEL); - if (!pcdev) - return -ENOMEM; - - pcdev->clk = clk; - - pcdev->pdata = pdev->dev.platform_data; - if (pcdev->pdata) { - pcdev->pflags = pcdev->pdata->flags; - pcdev->camexclk = pcdev->pdata->camexclk_khz * 1000; - } - - switch (pcdev->camexclk) { - case 6000000: - case 8000000: - case 9600000: - case 12000000: - case 24000000: - break; - default: - /* pcdev->camexclk != 0 => pcdev->pdata != NULL */ - dev_warn(&pdev->dev, - "Incorrect sensor clock frequency %ld kHz, " - "should be one of 0, 6, 8, 9.6, 12 or 24 MHz, " - "please correct your platform data\n", - pcdev->pdata->camexclk_khz); - pcdev->camexclk = 0; - case 0: - dev_info(&pdev->dev, "Not providing sensor clock\n"); - } - - INIT_LIST_HEAD(&pcdev->capture); - spin_lock_init(&pcdev->lock); - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(base)) - return PTR_ERR(base); - - pcdev->irq = irq; - pcdev->base = base; - - sensor_reset(pcdev, true); - - err = omap_request_dma(OMAP_DMA_CAMERA_IF_RX, DRIVER_NAME, - dma_isr, (void *)pcdev, &pcdev->dma_ch); - if (err < 0) { - dev_err(&pdev->dev, "Can't request DMA for OMAP1 Camera\n"); - return -EBUSY; - } - dev_dbg(&pdev->dev, "got DMA channel %d\n", pcdev->dma_ch); - - /* preconfigure DMA */ - omap_set_dma_src_params(pcdev->dma_ch, OMAP_DMA_PORT_TIPB, - OMAP_DMA_AMODE_CONSTANT, res->start + REG_CAMDATA, - 0, 0); - omap_set_dma_dest_burst_mode(pcdev->dma_ch, OMAP_DMA_DATA_BURST_4); - /* setup DMA autoinitialization */ - omap_dma_link_lch(pcdev->dma_ch, pcdev->dma_ch); - - err = devm_request_irq(&pdev->dev, pcdev->irq, cam_isr, 0, DRIVER_NAME, - pcdev); - if (err) { - dev_err(&pdev->dev, "Camera interrupt register failed\n"); - goto exit_free_dma; - } - - pcdev->soc_host.drv_name = DRIVER_NAME; - pcdev->soc_host.ops = &omap1_host_ops; - pcdev->soc_host.priv = pcdev; - pcdev->soc_host.v4l2_dev.dev = &pdev->dev; - pcdev->soc_host.nr = pdev->id; - - err = soc_camera_host_register(&pcdev->soc_host); - if (err) - return err; - - dev_info(&pdev->dev, "OMAP1 Camera Interface driver loaded\n"); - - return 0; - -exit_free_dma: - omap_free_dma(pcdev->dma_ch); -exit: - return err; -} - -static int omap1_cam_remove(struct platform_device *pdev) -{ - struct soc_camera_host *soc_host = to_soc_camera_host(&pdev->dev); - struct omap1_cam_dev *pcdev = container_of(soc_host, - struct omap1_cam_dev, soc_host); - - omap_free_dma(pcdev->dma_ch); - - soc_camera_host_unregister(soc_host); - - dev_info(&pdev->dev, "OMAP1 Camera Interface driver unloaded\n"); - - return 0; -} - -static struct platform_driver omap1_cam_driver = { - .driver = { - .name = DRIVER_NAME, - }, - .probe = omap1_cam_probe, - .remove = omap1_cam_remove, -}; - -module_platform_driver(omap1_cam_driver); - -module_param(sg_mode, bool, 0644); -MODULE_PARM_DESC(sg_mode, "videobuf mode, 0: dma-contig (default), 1: dma-sg"); - -MODULE_DESCRIPTION("OMAP1 Camera Interface driver"); -MODULE_AUTHOR("Janusz Krzysztofik <jkrzyszt@tis.icnet.pl>"); -MODULE_LICENSE("GPL v2"); -MODULE_VERSION(DRIVER_VERSION); -MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/staging/media/s5p-cec/Kconfig b/drivers/staging/media/s5p-cec/Kconfig new file mode 100644 index 000000000000..0315fd7ad0f1 --- /dev/null +++ b/drivers/staging/media/s5p-cec/Kconfig @@ -0,0 +1,9 @@ +config VIDEO_SAMSUNG_S5P_CEC + tristate "Samsung S5P CEC driver" + depends on VIDEO_DEV && MEDIA_CEC && (PLAT_S5P || ARCH_EXYNOS || COMPILE_TEST) + ---help--- + This is a driver for Samsung S5P HDMI CEC interface. It uses the + generic CEC framework interface. + CEC bus is present in the HDMI connector and enables communication + between compatible devices. + diff --git a/drivers/staging/media/s5p-cec/Makefile b/drivers/staging/media/s5p-cec/Makefile new file mode 100644 index 000000000000..0e2cf457825a --- /dev/null +++ b/drivers/staging/media/s5p-cec/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_VIDEO_SAMSUNG_S5P_CEC) += s5p-cec.o +s5p-cec-y += s5p_cec.o exynos_hdmi_cecctrl.o diff --git a/drivers/staging/media/s5p-cec/TODO b/drivers/staging/media/s5p-cec/TODO new file mode 100644 index 000000000000..7162f9ae0d26 --- /dev/null +++ b/drivers/staging/media/s5p-cec/TODO @@ -0,0 +1,3 @@ +There's nothing wrong on this driver, except that it depends on +the media staging core, that it is currently at staging. So, +this should be kept here while the core is not promoted. diff --git a/drivers/staging/media/s5p-cec/exynos_hdmi_cec.h b/drivers/staging/media/s5p-cec/exynos_hdmi_cec.h new file mode 100644 index 000000000000..3e4fc7b05e83 --- /dev/null +++ b/drivers/staging/media/s5p-cec/exynos_hdmi_cec.h @@ -0,0 +1,38 @@ +/* drivers/media/platform/s5p-cec/exynos_hdmi_cec.h + * + * Copyright (c) 2010, 2014 Samsung Electronics + * http://www.samsung.com/ + * + * Header file for interface of Samsung Exynos hdmi cec hardware + * + * 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. + */ + +#ifndef _EXYNOS_HDMI_CEC_H_ +#define _EXYNOS_HDMI_CEC_H_ __FILE__ + +#include <linux/regmap.h> +#include <linux/miscdevice.h> +#include "s5p_cec.h" + +void s5p_cec_set_divider(struct s5p_cec_dev *cec); +void s5p_cec_enable_rx(struct s5p_cec_dev *cec); +void s5p_cec_mask_rx_interrupts(struct s5p_cec_dev *cec); +void s5p_cec_unmask_rx_interrupts(struct s5p_cec_dev *cec); +void s5p_cec_mask_tx_interrupts(struct s5p_cec_dev *cec); +void s5p_cec_unmask_tx_interrupts(struct s5p_cec_dev *cec); +void s5p_cec_reset(struct s5p_cec_dev *cec); +void s5p_cec_tx_reset(struct s5p_cec_dev *cec); +void s5p_cec_rx_reset(struct s5p_cec_dev *cec); +void s5p_cec_threshold(struct s5p_cec_dev *cec); +void s5p_cec_copy_packet(struct s5p_cec_dev *cec, char *data, + size_t count, u8 retries); +void s5p_cec_set_addr(struct s5p_cec_dev *cec, u32 addr); +u32 s5p_cec_get_status(struct s5p_cec_dev *cec); +void s5p_clr_pending_tx(struct s5p_cec_dev *cec); +void s5p_clr_pending_rx(struct s5p_cec_dev *cec); +void s5p_cec_get_rx_buf(struct s5p_cec_dev *cec, u32 size, u8 *buffer); + +#endif /* _EXYNOS_HDMI_CEC_H_ */ diff --git a/drivers/staging/media/s5p-cec/exynos_hdmi_cecctrl.c b/drivers/staging/media/s5p-cec/exynos_hdmi_cecctrl.c new file mode 100644 index 000000000000..ce95e0fcd882 --- /dev/null +++ b/drivers/staging/media/s5p-cec/exynos_hdmi_cecctrl.c @@ -0,0 +1,209 @@ +/* drivers/media/platform/s5p-cec/exynos_hdmi_cecctrl.c + * + * Copyright (c) 2009, 2014 Samsung Electronics + * http://www.samsung.com/ + * + * cec ftn file for Samsung TVOUT driver + * + * 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. + */ + +#include <linux/io.h> +#include <linux/device.h> + +#include "exynos_hdmi_cec.h" +#include "regs-cec.h" + +#define S5P_HDMI_FIN 24000000 +#define CEC_DIV_RATIO 320000 + +#define CEC_MESSAGE_BROADCAST_MASK 0x0F +#define CEC_MESSAGE_BROADCAST 0x0F +#define CEC_FILTER_THRESHOLD 0x15 + +void s5p_cec_set_divider(struct s5p_cec_dev *cec) +{ + u32 div_ratio, div_val; + unsigned int reg; + + div_ratio = S5P_HDMI_FIN / CEC_DIV_RATIO - 1; + + if (regmap_read(cec->pmu, EXYNOS_HDMI_PHY_CONTROL, ®)) { + dev_err(cec->dev, "failed to read phy control\n"); + return; + } + + reg = (reg & ~(0x3FF << 16)) | (div_ratio << 16); + + if (regmap_write(cec->pmu, EXYNOS_HDMI_PHY_CONTROL, reg)) { + dev_err(cec->dev, "failed to write phy control\n"); + return; + } + + div_val = CEC_DIV_RATIO * 0.00005 - 1; + + writeb(0x0, cec->reg + S5P_CEC_DIVISOR_3); + writeb(0x0, cec->reg + S5P_CEC_DIVISOR_2); + writeb(0x0, cec->reg + S5P_CEC_DIVISOR_1); + writeb(div_val, cec->reg + S5P_CEC_DIVISOR_0); +} + +void s5p_cec_enable_rx(struct s5p_cec_dev *cec) +{ + u8 reg; + + reg = readb(cec->reg + S5P_CEC_RX_CTRL); + reg |= S5P_CEC_RX_CTRL_ENABLE; + writeb(reg, cec->reg + S5P_CEC_RX_CTRL); +} + +void s5p_cec_mask_rx_interrupts(struct s5p_cec_dev *cec) +{ + u8 reg; + + reg = readb(cec->reg + S5P_CEC_IRQ_MASK); + reg |= S5P_CEC_IRQ_RX_DONE; + reg |= S5P_CEC_IRQ_RX_ERROR; + writeb(reg, cec->reg + S5P_CEC_IRQ_MASK); +} + +void s5p_cec_unmask_rx_interrupts(struct s5p_cec_dev *cec) +{ + u8 reg; + + reg = readb(cec->reg + S5P_CEC_IRQ_MASK); + reg &= ~S5P_CEC_IRQ_RX_DONE; + reg &= ~S5P_CEC_IRQ_RX_ERROR; + writeb(reg, cec->reg + S5P_CEC_IRQ_MASK); +} + +void s5p_cec_mask_tx_interrupts(struct s5p_cec_dev *cec) +{ + u8 reg; + + reg = readb(cec->reg + S5P_CEC_IRQ_MASK); + reg |= S5P_CEC_IRQ_TX_DONE; + reg |= S5P_CEC_IRQ_TX_ERROR; + writeb(reg, cec->reg + S5P_CEC_IRQ_MASK); + +} + +void s5p_cec_unmask_tx_interrupts(struct s5p_cec_dev *cec) +{ + u8 reg; + + reg = readb(cec->reg + S5P_CEC_IRQ_MASK); + reg &= ~S5P_CEC_IRQ_TX_DONE; + reg &= ~S5P_CEC_IRQ_TX_ERROR; + writeb(reg, cec->reg + S5P_CEC_IRQ_MASK); +} + +void s5p_cec_reset(struct s5p_cec_dev *cec) +{ + u8 reg; + + writeb(S5P_CEC_RX_CTRL_RESET, cec->reg + S5P_CEC_RX_CTRL); + writeb(S5P_CEC_TX_CTRL_RESET, cec->reg + S5P_CEC_TX_CTRL); + + reg = readb(cec->reg + 0xc4); + reg &= ~0x1; + writeb(reg, cec->reg + 0xc4); +} + +void s5p_cec_tx_reset(struct s5p_cec_dev *cec) +{ + writeb(S5P_CEC_TX_CTRL_RESET, cec->reg + S5P_CEC_TX_CTRL); +} + +void s5p_cec_rx_reset(struct s5p_cec_dev *cec) +{ + u8 reg; + + writeb(S5P_CEC_RX_CTRL_RESET, cec->reg + S5P_CEC_RX_CTRL); + + reg = readb(cec->reg + 0xc4); + reg &= ~0x1; + writeb(reg, cec->reg + 0xc4); +} + +void s5p_cec_threshold(struct s5p_cec_dev *cec) +{ + writeb(CEC_FILTER_THRESHOLD, cec->reg + S5P_CEC_RX_FILTER_TH); + writeb(0, cec->reg + S5P_CEC_RX_FILTER_CTRL); +} + +void s5p_cec_copy_packet(struct s5p_cec_dev *cec, char *data, + size_t count, u8 retries) +{ + int i = 0; + u8 reg; + + while (i < count) { + writeb(data[i], cec->reg + (S5P_CEC_TX_BUFF0 + (i * 4))); + i++; + } + + writeb(count, cec->reg + S5P_CEC_TX_BYTES); + reg = readb(cec->reg + S5P_CEC_TX_CTRL); + reg |= S5P_CEC_TX_CTRL_START; + reg &= ~0x70; + reg |= retries << 4; + + if ((data[0] & CEC_MESSAGE_BROADCAST_MASK) == CEC_MESSAGE_BROADCAST) { + dev_dbg(cec->dev, "Broadcast"); + reg |= S5P_CEC_TX_CTRL_BCAST; + } else { + dev_dbg(cec->dev, "No Broadcast"); + reg &= ~S5P_CEC_TX_CTRL_BCAST; + } + + writeb(reg, cec->reg + S5P_CEC_TX_CTRL); + dev_dbg(cec->dev, "cec-tx: cec count (%zu): %*ph", count, + (int)count, data); +} + +void s5p_cec_set_addr(struct s5p_cec_dev *cec, u32 addr) +{ + writeb(addr & 0x0F, cec->reg + S5P_CEC_LOGIC_ADDR); +} + +u32 s5p_cec_get_status(struct s5p_cec_dev *cec) +{ + u32 status = 0; + + status = readb(cec->reg + S5P_CEC_STATUS_0); + status |= readb(cec->reg + S5P_CEC_STATUS_1) << 8; + status |= readb(cec->reg + S5P_CEC_STATUS_2) << 16; + status |= readb(cec->reg + S5P_CEC_STATUS_3) << 24; + + dev_dbg(cec->dev, "status = 0x%x!\n", status); + + return status; +} + +void s5p_clr_pending_tx(struct s5p_cec_dev *cec) +{ + writeb(S5P_CEC_IRQ_TX_DONE | S5P_CEC_IRQ_TX_ERROR, + cec->reg + S5P_CEC_IRQ_CLEAR); +} + +void s5p_clr_pending_rx(struct s5p_cec_dev *cec) +{ + writeb(S5P_CEC_IRQ_RX_DONE | S5P_CEC_IRQ_RX_ERROR, + cec->reg + S5P_CEC_IRQ_CLEAR); +} + +void s5p_cec_get_rx_buf(struct s5p_cec_dev *cec, u32 size, u8 *buffer) +{ + u32 i = 0; + char debug[40]; + + while (i < size) { + buffer[i] = readb(cec->reg + S5P_CEC_RX_BUFF0 + (i * 4)); + sprintf(debug + i * 2, "%02x ", buffer[i]); + i++; + } + dev_dbg(cec->dev, "cec-rx: cec size(%d): %s", size, debug); +} diff --git a/drivers/staging/media/s5p-cec/regs-cec.h b/drivers/staging/media/s5p-cec/regs-cec.h new file mode 100644 index 000000000000..b2e7e129920e --- /dev/null +++ b/drivers/staging/media/s5p-cec/regs-cec.h @@ -0,0 +1,96 @@ +/* drivers/media/platform/s5p-cec/regs-cec.h + * + * Copyright (c) 2010 Samsung Electronics + * http://www.samsung.com/ + * + * register header file for Samsung TVOUT driver + * + * 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. + */ + +#ifndef __EXYNOS_REGS__H +#define __EXYNOS_REGS__H + +/* + * Register part + */ +#define S5P_CEC_STATUS_0 (0x0000) +#define S5P_CEC_STATUS_1 (0x0004) +#define S5P_CEC_STATUS_2 (0x0008) +#define S5P_CEC_STATUS_3 (0x000C) +#define S5P_CEC_IRQ_MASK (0x0010) +#define S5P_CEC_IRQ_CLEAR (0x0014) +#define S5P_CEC_LOGIC_ADDR (0x0020) +#define S5P_CEC_DIVISOR_0 (0x0030) +#define S5P_CEC_DIVISOR_1 (0x0034) +#define S5P_CEC_DIVISOR_2 (0x0038) +#define S5P_CEC_DIVISOR_3 (0x003C) + +#define S5P_CEC_TX_CTRL (0x0040) +#define S5P_CEC_TX_BYTES (0x0044) +#define S5P_CEC_TX_STAT0 (0x0060) +#define S5P_CEC_TX_STAT1 (0x0064) +#define S5P_CEC_TX_BUFF0 (0x0080) +#define S5P_CEC_TX_BUFF1 (0x0084) +#define S5P_CEC_TX_BUFF2 (0x0088) +#define S5P_CEC_TX_BUFF3 (0x008C) +#define S5P_CEC_TX_BUFF4 (0x0090) +#define S5P_CEC_TX_BUFF5 (0x0094) +#define S5P_CEC_TX_BUFF6 (0x0098) +#define S5P_CEC_TX_BUFF7 (0x009C) +#define S5P_CEC_TX_BUFF8 (0x00A0) +#define S5P_CEC_TX_BUFF9 (0x00A4) +#define S5P_CEC_TX_BUFF10 (0x00A8) +#define S5P_CEC_TX_BUFF11 (0x00AC) +#define S5P_CEC_TX_BUFF12 (0x00B0) +#define S5P_CEC_TX_BUFF13 (0x00B4) +#define S5P_CEC_TX_BUFF14 (0x00B8) +#define S5P_CEC_TX_BUFF15 (0x00BC) + +#define S5P_CEC_RX_CTRL (0x00C0) +#define S5P_CEC_RX_STAT0 (0x00E0) +#define S5P_CEC_RX_STAT1 (0x00E4) +#define S5P_CEC_RX_BUFF0 (0x0100) +#define S5P_CEC_RX_BUFF1 (0x0104) +#define S5P_CEC_RX_BUFF2 (0x0108) +#define S5P_CEC_RX_BUFF3 (0x010C) +#define S5P_CEC_RX_BUFF4 (0x0110) +#define S5P_CEC_RX_BUFF5 (0x0114) +#define S5P_CEC_RX_BUFF6 (0x0118) +#define S5P_CEC_RX_BUFF7 (0x011C) +#define S5P_CEC_RX_BUFF8 (0x0120) +#define S5P_CEC_RX_BUFF9 (0x0124) +#define S5P_CEC_RX_BUFF10 (0x0128) +#define S5P_CEC_RX_BUFF11 (0x012C) +#define S5P_CEC_RX_BUFF12 (0x0130) +#define S5P_CEC_RX_BUFF13 (0x0134) +#define S5P_CEC_RX_BUFF14 (0x0138) +#define S5P_CEC_RX_BUFF15 (0x013C) + +#define S5P_CEC_RX_FILTER_CTRL (0x0180) +#define S5P_CEC_RX_FILTER_TH (0x0184) + +/* + * Bit definition part + */ +#define S5P_CEC_IRQ_TX_DONE (1<<0) +#define S5P_CEC_IRQ_TX_ERROR (1<<1) +#define S5P_CEC_IRQ_RX_DONE (1<<4) +#define S5P_CEC_IRQ_RX_ERROR (1<<5) + +#define S5P_CEC_TX_CTRL_START (1<<0) +#define S5P_CEC_TX_CTRL_BCAST (1<<1) +#define S5P_CEC_TX_CTRL_RETRY (0x04<<4) +#define S5P_CEC_TX_CTRL_RESET (1<<7) + +#define S5P_CEC_RX_CTRL_ENABLE (1<<0) +#define S5P_CEC_RX_CTRL_RESET (1<<7) + +#define S5P_CEC_LOGIC_ADDR_MASK (0xF) + +/* PMU Registers for PHY */ +#define EXYNOS_HDMI_PHY_CONTROL 0x700 + +#endif /* __EXYNOS_REGS__H */ diff --git a/drivers/staging/media/s5p-cec/s5p_cec.c b/drivers/staging/media/s5p-cec/s5p_cec.c new file mode 100644 index 000000000000..f90b7c4e48fe --- /dev/null +++ b/drivers/staging/media/s5p-cec/s5p_cec.c @@ -0,0 +1,294 @@ +/* drivers/media/platform/s5p-cec/s5p_cec.c + * + * Samsung S5P CEC driver + * + * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * + * 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 driver is based on the "cec interface driver for exynos soc" by + * SangPil Moon. + */ + +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#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> + +#include "exynos_hdmi_cec.h" +#include "regs-cec.h" +#include "s5p_cec.h" + +#define CEC_NAME "s5p-cec" + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "debug level (0-2)"); + +static int s5p_cec_adap_enable(struct cec_adapter *adap, bool enable) +{ + struct s5p_cec_dev *cec = adap->priv; + + if (enable) { + pm_runtime_get_sync(cec->dev); + + s5p_cec_reset(cec); + + s5p_cec_set_divider(cec); + s5p_cec_threshold(cec); + + s5p_cec_unmask_tx_interrupts(cec); + s5p_cec_unmask_rx_interrupts(cec); + s5p_cec_enable_rx(cec); + } else { + s5p_cec_mask_tx_interrupts(cec); + s5p_cec_mask_rx_interrupts(cec); + pm_runtime_disable(cec->dev); + } + + return 0; +} + +static int s5p_cec_adap_log_addr(struct cec_adapter *adap, u8 addr) +{ + struct s5p_cec_dev *cec = adap->priv; + + s5p_cec_set_addr(cec, addr); + return 0; +} + +static int s5p_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, + u32 signal_free_time, struct cec_msg *msg) +{ + struct s5p_cec_dev *cec = adap->priv; + + /* + * Unclear if 0 retries are allowed by the hardware, so have 1 as + * the minimum. + */ + s5p_cec_copy_packet(cec, msg->msg, msg->len, max(1, attempts - 1)); + return 0; +} + +static irqreturn_t s5p_cec_irq_handler(int irq, void *priv) +{ + struct s5p_cec_dev *cec = priv; + u32 status = 0; + + status = s5p_cec_get_status(cec); + + dev_dbg(cec->dev, "irq received\n"); + + if (status & CEC_STATUS_TX_DONE) { + if (status & CEC_STATUS_TX_ERROR) { + dev_dbg(cec->dev, "CEC_STATUS_TX_ERROR set\n"); + cec->tx = STATE_ERROR; + } else { + dev_dbg(cec->dev, "CEC_STATUS_TX_DONE\n"); + cec->tx = STATE_DONE; + } + s5p_clr_pending_tx(cec); + } + + if (status & CEC_STATUS_RX_DONE) { + if (status & CEC_STATUS_RX_ERROR) { + dev_dbg(cec->dev, "CEC_STATUS_RX_ERROR set\n"); + s5p_cec_rx_reset(cec); + s5p_cec_enable_rx(cec); + } else { + dev_dbg(cec->dev, "CEC_STATUS_RX_DONE set\n"); + if (cec->rx != STATE_IDLE) + dev_dbg(cec->dev, "Buffer overrun (worker did not process previous message)\n"); + cec->rx = STATE_BUSY; + cec->msg.len = status >> 24; + cec->msg.rx_status = CEC_RX_STATUS_OK; + s5p_cec_get_rx_buf(cec, cec->msg.len, + cec->msg.msg); + cec->rx = STATE_DONE; + s5p_cec_enable_rx(cec); + } + /* Clear interrupt pending bit */ + s5p_clr_pending_rx(cec); + } + return IRQ_WAKE_THREAD; +} + +static irqreturn_t s5p_cec_irq_handler_thread(int irq, void *priv) +{ + struct s5p_cec_dev *cec = priv; + + dev_dbg(cec->dev, "irq processing thread\n"); + switch (cec->tx) { + case STATE_DONE: + cec_transmit_done(cec->adap, CEC_TX_STATUS_OK, 0, 0, 0, 0); + cec->tx = STATE_IDLE; + break; + case STATE_ERROR: + cec_transmit_done(cec->adap, + CEC_TX_STATUS_MAX_RETRIES | CEC_TX_STATUS_ERROR, + 0, 0, 0, 1); + cec->tx = STATE_IDLE; + break; + case STATE_BUSY: + dev_err(cec->dev, "state set to busy, this should not occur here\n"); + break; + default: + break; + } + + switch (cec->rx) { + case STATE_DONE: + cec_received_msg(cec->adap, &cec->msg); + cec->rx = STATE_IDLE; + break; + default: + break; + } + + return IRQ_HANDLED; +} + +static const struct cec_adap_ops s5p_cec_adap_ops = { + .adap_enable = s5p_cec_adap_enable, + .adap_log_addr = s5p_cec_adap_log_addr, + .adap_transmit = s5p_cec_adap_transmit, +}; + +static int s5p_cec_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct s5p_cec_dev *cec; + int ret; + + cec = devm_kzalloc(&pdev->dev, sizeof(*cec), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + cec->dev = dev; + + cec->irq = platform_get_irq(pdev, 0); + if (cec->irq < 0) + return cec->irq; + + ret = devm_request_threaded_irq(dev, cec->irq, s5p_cec_irq_handler, + s5p_cec_irq_handler_thread, 0, pdev->name, cec); + if (ret) + return ret; + + cec->clk = devm_clk_get(dev, "hdmicec"); + if (IS_ERR(cec->clk)) + return PTR_ERR(cec->clk); + + cec->pmu = syscon_regmap_lookup_by_phandle(dev->of_node, + "samsung,syscon-phandle"); + if (IS_ERR(cec->pmu)) + return -EPROBE_DEFER; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + cec->reg = devm_ioremap_resource(dev, res); + if (IS_ERR(cec->reg)) + return PTR_ERR(cec->reg); + + cec->adap = cec_allocate_adapter(&s5p_cec_adap_ops, cec, + CEC_NAME, + CEC_CAP_PHYS_ADDR | CEC_CAP_LOG_ADDRS | CEC_CAP_TRANSMIT | + CEC_CAP_PASSTHROUGH | CEC_CAP_RC, + 1, &pdev->dev); + ret = PTR_ERR_OR_ZERO(cec->adap); + if (ret) + return ret; + ret = cec_register_adapter(cec->adap); + if (ret) { + cec_delete_adapter(cec->adap); + return ret; + } + + platform_set_drvdata(pdev, cec); + pm_runtime_enable(dev); + + dev_dbg(dev, "successfuly probed\n"); + return 0; +} + +static int s5p_cec_remove(struct platform_device *pdev) +{ + struct s5p_cec_dev *cec = platform_get_drvdata(pdev); + + cec_unregister_adapter(cec->adap); + pm_runtime_disable(&pdev->dev); + return 0; +} + +static int s5p_cec_runtime_suspend(struct device *dev) +{ + struct s5p_cec_dev *cec = dev_get_drvdata(dev); + + clk_disable_unprepare(cec->clk); + return 0; +} + +static int s5p_cec_runtime_resume(struct device *dev) +{ + struct s5p_cec_dev *cec = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(cec->clk); + if (ret < 0) + return ret; + return 0; +} + +static int s5p_cec_suspend(struct device *dev) +{ + if (pm_runtime_suspended(dev)) + return 0; + return s5p_cec_runtime_suspend(dev); +} + +static int s5p_cec_resume(struct device *dev) +{ + if (pm_runtime_suspended(dev)) + return 0; + return s5p_cec_runtime_resume(dev); +} + +static const struct dev_pm_ops s5p_cec_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(s5p_cec_suspend, s5p_cec_resume) + SET_RUNTIME_PM_OPS(s5p_cec_runtime_suspend, s5p_cec_runtime_resume, + NULL) +}; + +static const struct of_device_id s5p_cec_match[] = { + { + .compatible = "samsung,s5p-cec", + }, + {}, +}; + +static struct platform_driver s5p_cec_pdrv = { + .probe = s5p_cec_probe, + .remove = s5p_cec_remove, + .driver = { + .name = CEC_NAME, + .of_match_table = s5p_cec_match, + .pm = &s5p_cec_pm_ops, + }, +}; + +module_platform_driver(s5p_cec_pdrv); + +MODULE_AUTHOR("Kamil Debski <kamil@wypas.org>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Samsung S5P CEC driver"); diff --git a/drivers/staging/media/s5p-cec/s5p_cec.h b/drivers/staging/media/s5p-cec/s5p_cec.h new file mode 100644 index 000000000000..03732c13d19f --- /dev/null +++ b/drivers/staging/media/s5p-cec/s5p_cec.h @@ -0,0 +1,76 @@ +/* drivers/media/platform/s5p-cec/s5p_cec.h + * + * Samsung S5P HDMI CEC driver + * + * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * + * 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 _S5P_CEC_H_ +#define _S5P_CEC_H_ __FILE__ + +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#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> + +#include "exynos_hdmi_cec.h" +#include "regs-cec.h" +#include "s5p_cec.h" + +#define CEC_NAME "s5p-cec" + +#define CEC_STATUS_TX_RUNNING (1 << 0) +#define CEC_STATUS_TX_TRANSFERRING (1 << 1) +#define CEC_STATUS_TX_DONE (1 << 2) +#define CEC_STATUS_TX_ERROR (1 << 3) +#define CEC_STATUS_TX_BYTES (0xFF << 8) +#define CEC_STATUS_RX_RUNNING (1 << 16) +#define CEC_STATUS_RX_RECEIVING (1 << 17) +#define CEC_STATUS_RX_DONE (1 << 18) +#define CEC_STATUS_RX_ERROR (1 << 19) +#define CEC_STATUS_RX_BCAST (1 << 20) +#define CEC_STATUS_RX_BYTES (0xFF << 24) + +#define CEC_WORKER_TX_DONE (1 << 0) +#define CEC_WORKER_RX_MSG (1 << 1) + +/* CEC Rx buffer size */ +#define CEC_RX_BUFF_SIZE 16 +/* CEC Tx buffer size */ +#define CEC_TX_BUFF_SIZE 16 + +enum cec_state { + STATE_IDLE, + STATE_BUSY, + STATE_DONE, + STATE_ERROR +}; + +struct s5p_cec_dev { + struct cec_adapter *adap; + struct clk *clk; + struct device *dev; + struct mutex lock; + struct regmap *pmu; + int irq; + void __iomem *reg; + + enum cec_state rx; + enum cec_state tx; + struct cec_msg msg; +}; + +#endif /* _S5P_CEC_H_ */ diff --git a/drivers/staging/media/timb/Kconfig b/drivers/staging/media/timb/Kconfig deleted file mode 100644 index e413fecc1e67..000000000000 --- a/drivers/staging/media/timb/Kconfig +++ /dev/null @@ -1,11 +0,0 @@ -config VIDEO_TIMBERDALE - tristate "Support for timberdale Video In/LogiWIN" - depends on VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API && HAS_DMA - depends on (MFD_TIMBERDALE && TIMB_DMA) || COMPILE_TEST - select VIDEO_ADV7180 - select VIDEOBUF_DMA_CONTIG - ---help--- - Add support for the Video In peripherial of the timberdale FPGA. - - This driver is deprecated and will be removed soon unless someone - will start the work to convert this driver to the vb2 framework. diff --git a/drivers/staging/media/timb/Makefile b/drivers/staging/media/timb/Makefile deleted file mode 100644 index 4c989c23a0e0..000000000000 --- a/drivers/staging/media/timb/Makefile +++ /dev/null @@ -1 +0,0 @@ -obj-$(CONFIG_VIDEO_TIMBERDALE) += timblogiw.o diff --git a/drivers/staging/media/timb/timblogiw.c b/drivers/staging/media/timb/timblogiw.c deleted file mode 100644 index 113c9f3c0b3e..000000000000 --- a/drivers/staging/media/timb/timblogiw.c +++ /dev/null @@ -1,870 +0,0 @@ -/* - * timblogiw.c timberdale FPGA LogiWin Video In driver - * Copyright (c) 2009-2010 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -/* Supports: - * Timberdale FPGA LogiWin Video In - */ - -#include <linux/platform_device.h> -#include <linux/slab.h> -#include <linux/dmaengine.h> -#include <linux/scatterlist.h> -#include <linux/interrupt.h> -#include <linux/list.h> -#include <linux/i2c.h> -#include <linux/module.h> -#include <media/v4l2-ioctl.h> -#include <media/v4l2-device.h> -#include <media/videobuf-dma-contig.h> -#include <linux/platform_data/media/timb_video.h> - -#define DRIVER_NAME "timb-video" - -#define TIMBLOGIWIN_NAME "Timberdale Video-In" -#define TIMBLOGIW_VERSION_CODE 0x04 - -#define TIMBLOGIW_LINES_PER_DESC 44 -#define TIMBLOGIW_MAX_VIDEO_MEM 16 - -#define TIMBLOGIW_HAS_DECODER(lw) (lw->pdata.encoder.module_name) - - -struct timblogiw { - struct video_device video_dev; - struct v4l2_device v4l2_dev; /* mutual exclusion */ - struct mutex lock; - struct device *dev; - struct timb_video_platform_data pdata; - struct v4l2_subdev *sd_enc; /* encoder */ - bool opened; -}; - -struct timblogiw_tvnorm { - v4l2_std_id std; - u16 width; - u16 height; - u8 fps; -}; - -struct timblogiw_fh { - struct videobuf_queue vb_vidq; - struct timblogiw_tvnorm const *cur_norm; - struct list_head capture; - struct dma_chan *chan; - spinlock_t queue_lock; /* mutual exclusion */ - unsigned int frame_count; -}; - -struct timblogiw_buffer { - /* common v4l buffer stuff -- must be first */ - struct videobuf_buffer vb; - struct scatterlist sg[16]; - dma_cookie_t cookie; - struct timblogiw_fh *fh; -}; - -static const struct timblogiw_tvnorm timblogiw_tvnorms[] = { - { - .std = V4L2_STD_PAL, - .width = 720, - .height = 576, - .fps = 25 - }, - { - .std = V4L2_STD_NTSC, - .width = 720, - .height = 480, - .fps = 30 - } -}; - -static int timblogiw_bytes_per_line(const struct timblogiw_tvnorm *norm) -{ - return norm->width * 2; -} - - -static int timblogiw_frame_size(const struct timblogiw_tvnorm *norm) -{ - return norm->height * timblogiw_bytes_per_line(norm); -} - -static const struct timblogiw_tvnorm *timblogiw_get_norm(const v4l2_std_id std) -{ - int i; - for (i = 0; i < ARRAY_SIZE(timblogiw_tvnorms); i++) - if (timblogiw_tvnorms[i].std & std) - return timblogiw_tvnorms + i; - - /* default to first element */ - return timblogiw_tvnorms; -} - -static void timblogiw_dma_cb(void *data) -{ - struct timblogiw_buffer *buf = data; - struct timblogiw_fh *fh = buf->fh; - struct videobuf_buffer *vb = &buf->vb; - - spin_lock(&fh->queue_lock); - - /* mark the transfer done */ - buf->cookie = -1; - - fh->frame_count++; - - if (vb->state != VIDEOBUF_ERROR) { - list_del(&vb->queue); - v4l2_get_timestamp(&vb->ts); - vb->field_count = fh->frame_count * 2; - vb->state = VIDEOBUF_DONE; - - wake_up(&vb->done); - } - - if (!list_empty(&fh->capture)) { - vb = list_entry(fh->capture.next, struct videobuf_buffer, - queue); - vb->state = VIDEOBUF_ACTIVE; - } - - spin_unlock(&fh->queue_lock); -} - -static bool timblogiw_dma_filter_fn(struct dma_chan *chan, void *filter_param) -{ - return chan->chan_id == (uintptr_t)filter_param; -} - -/* IOCTL functions */ - -static int timblogiw_g_fmt(struct file *file, void *priv, - struct v4l2_format *format) -{ - struct video_device *vdev = video_devdata(file); - struct timblogiw *lw = video_get_drvdata(vdev); - struct timblogiw_fh *fh = priv; - - dev_dbg(&vdev->dev, "%s entry\n", __func__); - - if (format->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) - return -EINVAL; - - mutex_lock(&lw->lock); - - format->fmt.pix.width = fh->cur_norm->width; - format->fmt.pix.height = fh->cur_norm->height; - format->fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY; - format->fmt.pix.bytesperline = timblogiw_bytes_per_line(fh->cur_norm); - format->fmt.pix.sizeimage = timblogiw_frame_size(fh->cur_norm); - format->fmt.pix.field = V4L2_FIELD_NONE; - - mutex_unlock(&lw->lock); - - return 0; -} - -static int timblogiw_try_fmt(struct file *file, void *priv, - struct v4l2_format *format) -{ - struct video_device *vdev = video_devdata(file); - struct v4l2_pix_format *pix = &format->fmt.pix; - - dev_dbg(&vdev->dev, - "%s - width=%d, height=%d, pixelformat=%d, field=%d\n" - "bytes per line %d, size image: %d, colorspace: %d\n", - __func__, - pix->width, pix->height, pix->pixelformat, pix->field, - pix->bytesperline, pix->sizeimage, pix->colorspace); - - if (format->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) - return -EINVAL; - - if (pix->field != V4L2_FIELD_NONE) - return -EINVAL; - - if (pix->pixelformat != V4L2_PIX_FMT_UYVY) - return -EINVAL; - - return 0; -} - -static int timblogiw_s_fmt(struct file *file, void *priv, - struct v4l2_format *format) -{ - struct video_device *vdev = video_devdata(file); - struct timblogiw *lw = video_get_drvdata(vdev); - struct timblogiw_fh *fh = priv; - struct v4l2_pix_format *pix = &format->fmt.pix; - int err; - - mutex_lock(&lw->lock); - - err = timblogiw_try_fmt(file, priv, format); - if (err) - goto out; - - if (videobuf_queue_is_busy(&fh->vb_vidq)) { - dev_err(&vdev->dev, "%s queue busy\n", __func__); - err = -EBUSY; - goto out; - } - - pix->width = fh->cur_norm->width; - pix->height = fh->cur_norm->height; - -out: - mutex_unlock(&lw->lock); - return err; -} - -static int timblogiw_querycap(struct file *file, void *priv, - struct v4l2_capability *cap) -{ - struct video_device *vdev = video_devdata(file); - - dev_dbg(&vdev->dev, "%s: Entry\n", __func__); - strncpy(cap->card, TIMBLOGIWIN_NAME, sizeof(cap->card)-1); - strncpy(cap->driver, DRIVER_NAME, sizeof(cap->driver) - 1); - snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", vdev->name); - cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | - V4L2_CAP_READWRITE; - cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; - - return 0; -} - -static int timblogiw_enum_fmt(struct file *file, void *priv, - struct v4l2_fmtdesc *fmt) -{ - struct video_device *vdev = video_devdata(file); - - dev_dbg(&vdev->dev, "%s, index: %d\n", __func__, fmt->index); - - if (fmt->index != 0) - return -EINVAL; - memset(fmt, 0, sizeof(*fmt)); - fmt->index = 0; - fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - strncpy(fmt->description, "4:2:2, packed, YUYV", - sizeof(fmt->description)-1); - fmt->pixelformat = V4L2_PIX_FMT_UYVY; - - return 0; -} - -static int timblogiw_g_parm(struct file *file, void *priv, - struct v4l2_streamparm *sp) -{ - struct timblogiw_fh *fh = priv; - struct v4l2_captureparm *cp = &sp->parm.capture; - - cp->capability = V4L2_CAP_TIMEPERFRAME; - cp->timeperframe.numerator = 1; - cp->timeperframe.denominator = fh->cur_norm->fps; - - return 0; -} - -static int timblogiw_reqbufs(struct file *file, void *priv, - struct v4l2_requestbuffers *rb) -{ - struct video_device *vdev = video_devdata(file); - struct timblogiw_fh *fh = priv; - - dev_dbg(&vdev->dev, "%s: entry\n", __func__); - - return videobuf_reqbufs(&fh->vb_vidq, rb); -} - -static int timblogiw_querybuf(struct file *file, void *priv, - struct v4l2_buffer *b) -{ - struct video_device *vdev = video_devdata(file); - struct timblogiw_fh *fh = priv; - - dev_dbg(&vdev->dev, "%s: entry\n", __func__); - - return videobuf_querybuf(&fh->vb_vidq, b); -} - -static int timblogiw_qbuf(struct file *file, void *priv, struct v4l2_buffer *b) -{ - struct video_device *vdev = video_devdata(file); - struct timblogiw_fh *fh = priv; - - dev_dbg(&vdev->dev, "%s: entry\n", __func__); - - return videobuf_qbuf(&fh->vb_vidq, b); -} - -static int timblogiw_dqbuf(struct file *file, void *priv, - struct v4l2_buffer *b) -{ - struct video_device *vdev = video_devdata(file); - struct timblogiw_fh *fh = priv; - - dev_dbg(&vdev->dev, "%s: entry\n", __func__); - - return videobuf_dqbuf(&fh->vb_vidq, b, file->f_flags & O_NONBLOCK); -} - -static int timblogiw_g_std(struct file *file, void *priv, v4l2_std_id *std) -{ - struct video_device *vdev = video_devdata(file); - struct timblogiw_fh *fh = priv; - - dev_dbg(&vdev->dev, "%s: entry\n", __func__); - - *std = fh->cur_norm->std; - return 0; -} - -static int timblogiw_s_std(struct file *file, void *priv, v4l2_std_id std) -{ - struct video_device *vdev = video_devdata(file); - struct timblogiw *lw = video_get_drvdata(vdev); - struct timblogiw_fh *fh = priv; - int err = 0; - - dev_dbg(&vdev->dev, "%s: entry\n", __func__); - - mutex_lock(&lw->lock); - - if (TIMBLOGIW_HAS_DECODER(lw)) - err = v4l2_subdev_call(lw->sd_enc, video, s_std, std); - - if (!err) - fh->cur_norm = timblogiw_get_norm(std); - - mutex_unlock(&lw->lock); - - return err; -} - -static int timblogiw_enuminput(struct file *file, void *priv, - struct v4l2_input *inp) -{ - struct video_device *vdev = video_devdata(file); - int i; - - dev_dbg(&vdev->dev, "%s: Entry\n", __func__); - - if (inp->index != 0) - return -EINVAL; - - inp->index = 0; - - strncpy(inp->name, "Timb input 1", sizeof(inp->name) - 1); - inp->type = V4L2_INPUT_TYPE_CAMERA; - - inp->std = 0; - for (i = 0; i < ARRAY_SIZE(timblogiw_tvnorms); i++) - inp->std |= timblogiw_tvnorms[i].std; - - return 0; -} - -static int timblogiw_g_input(struct file *file, void *priv, - unsigned int *input) -{ - struct video_device *vdev = video_devdata(file); - - dev_dbg(&vdev->dev, "%s: Entry\n", __func__); - - *input = 0; - - return 0; -} - -static int timblogiw_s_input(struct file *file, void *priv, unsigned int input) -{ - struct video_device *vdev = video_devdata(file); - - dev_dbg(&vdev->dev, "%s: Entry\n", __func__); - - if (input != 0) - return -EINVAL; - return 0; -} - -static int timblogiw_streamon(struct file *file, void *priv, enum v4l2_buf_type type) -{ - struct video_device *vdev = video_devdata(file); - struct timblogiw_fh *fh = priv; - - dev_dbg(&vdev->dev, "%s: entry\n", __func__); - - if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { - dev_dbg(&vdev->dev, "%s - No capture device\n", __func__); - return -EINVAL; - } - - fh->frame_count = 0; - return videobuf_streamon(&fh->vb_vidq); -} - -static int timblogiw_streamoff(struct file *file, void *priv, - enum v4l2_buf_type type) -{ - struct video_device *vdev = video_devdata(file); - struct timblogiw_fh *fh = priv; - - dev_dbg(&vdev->dev, "%s entry\n", __func__); - - if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE) - return -EINVAL; - - return videobuf_streamoff(&fh->vb_vidq); -} - -static int timblogiw_querystd(struct file *file, void *priv, v4l2_std_id *std) -{ - struct video_device *vdev = video_devdata(file); - struct timblogiw *lw = video_get_drvdata(vdev); - struct timblogiw_fh *fh = priv; - - dev_dbg(&vdev->dev, "%s entry\n", __func__); - - if (TIMBLOGIW_HAS_DECODER(lw)) - return v4l2_subdev_call(lw->sd_enc, video, querystd, std); - else { - *std = fh->cur_norm->std; - return 0; - } -} - -static int timblogiw_enum_framesizes(struct file *file, void *priv, - struct v4l2_frmsizeenum *fsize) -{ - struct video_device *vdev = video_devdata(file); - struct timblogiw_fh *fh = priv; - - dev_dbg(&vdev->dev, "%s - index: %d, format: %d\n", __func__, - fsize->index, fsize->pixel_format); - - if ((fsize->index != 0) || - (fsize->pixel_format != V4L2_PIX_FMT_UYVY)) - return -EINVAL; - - fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; - fsize->discrete.width = fh->cur_norm->width; - fsize->discrete.height = fh->cur_norm->height; - - return 0; -} - -/* Video buffer functions */ - -static int buffer_setup(struct videobuf_queue *vq, unsigned int *count, - unsigned int *size) -{ - struct timblogiw_fh *fh = vq->priv_data; - - *size = timblogiw_frame_size(fh->cur_norm); - - if (!*count) - *count = 32; - - while (*size * *count > TIMBLOGIW_MAX_VIDEO_MEM * 1024 * 1024) - (*count)--; - - return 0; -} - -static int buffer_prepare(struct videobuf_queue *vq, struct videobuf_buffer *vb, - enum v4l2_field field) -{ - struct timblogiw_fh *fh = vq->priv_data; - struct timblogiw_buffer *buf = container_of(vb, struct timblogiw_buffer, - vb); - unsigned int data_size = timblogiw_frame_size(fh->cur_norm); - int err = 0; - - if (vb->baddr && vb->bsize < data_size) - /* User provided buffer, but it is too small */ - return -ENOMEM; - - vb->size = data_size; - vb->width = fh->cur_norm->width; - vb->height = fh->cur_norm->height; - vb->field = field; - - if (vb->state == VIDEOBUF_NEEDS_INIT) { - int i; - unsigned int size; - unsigned int bytes_per_desc = TIMBLOGIW_LINES_PER_DESC * - timblogiw_bytes_per_line(fh->cur_norm); - dma_addr_t addr; - - sg_init_table(buf->sg, ARRAY_SIZE(buf->sg)); - - err = videobuf_iolock(vq, vb, NULL); - if (err) - goto err; - - addr = videobuf_to_dma_contig(vb); - for (i = 0, size = 0; size < data_size; i++) { - sg_dma_address(buf->sg + i) = addr + size; - size += bytes_per_desc; - sg_dma_len(buf->sg + i) = (size > data_size) ? - (bytes_per_desc - (size - data_size)) : - bytes_per_desc; - } - - vb->state = VIDEOBUF_PREPARED; - buf->cookie = -1; - buf->fh = fh; - } - - return 0; - -err: - videobuf_dma_contig_free(vq, vb); - vb->state = VIDEOBUF_NEEDS_INIT; - return err; -} - -static void buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb) -{ - struct timblogiw_fh *fh = vq->priv_data; - struct timblogiw_buffer *buf = container_of(vb, struct timblogiw_buffer, - vb); - struct dma_async_tx_descriptor *desc; - int sg_elems; - int bytes_per_desc = TIMBLOGIW_LINES_PER_DESC * - timblogiw_bytes_per_line(fh->cur_norm); - - sg_elems = timblogiw_frame_size(fh->cur_norm) / bytes_per_desc; - sg_elems += - (timblogiw_frame_size(fh->cur_norm) % bytes_per_desc) ? 1 : 0; - - if (list_empty(&fh->capture)) - vb->state = VIDEOBUF_ACTIVE; - else - vb->state = VIDEOBUF_QUEUED; - - list_add_tail(&vb->queue, &fh->capture); - - spin_unlock_irq(&fh->queue_lock); - - desc = dmaengine_prep_slave_sg(fh->chan, - buf->sg, sg_elems, DMA_DEV_TO_MEM, - DMA_PREP_INTERRUPT); - if (!desc) { - spin_lock_irq(&fh->queue_lock); - list_del_init(&vb->queue); - vb->state = VIDEOBUF_PREPARED; - return; - } - - desc->callback_param = buf; - desc->callback = timblogiw_dma_cb; - - buf->cookie = desc->tx_submit(desc); - - spin_lock_irq(&fh->queue_lock); -} - -static void buffer_release(struct videobuf_queue *vq, - struct videobuf_buffer *vb) -{ - struct timblogiw_fh *fh = vq->priv_data; - struct timblogiw_buffer *buf = container_of(vb, struct timblogiw_buffer, - vb); - - videobuf_waiton(vq, vb, 0, 0); - if (buf->cookie >= 0) - dma_sync_wait(fh->chan, buf->cookie); - - videobuf_dma_contig_free(vq, vb); - vb->state = VIDEOBUF_NEEDS_INIT; -} - -static struct videobuf_queue_ops timblogiw_video_qops = { - .buf_setup = buffer_setup, - .buf_prepare = buffer_prepare, - .buf_queue = buffer_queue, - .buf_release = buffer_release, -}; - -/* Device Operations functions */ - -static int timblogiw_open(struct file *file) -{ - struct video_device *vdev = video_devdata(file); - struct timblogiw *lw = video_get_drvdata(vdev); - struct timblogiw_fh *fh; - v4l2_std_id std; - dma_cap_mask_t mask; - int err = 0; - - dev_dbg(&vdev->dev, "%s: entry\n", __func__); - - mutex_lock(&lw->lock); - if (lw->opened) { - err = -EBUSY; - goto out; - } - - if (TIMBLOGIW_HAS_DECODER(lw) && !lw->sd_enc) { - struct i2c_adapter *adapt; - - /* find the video decoder */ - adapt = i2c_get_adapter(lw->pdata.i2c_adapter); - if (!adapt) { - dev_err(&vdev->dev, "No I2C bus #%d\n", - lw->pdata.i2c_adapter); - err = -ENODEV; - goto out; - } - - /* now find the encoder */ - lw->sd_enc = v4l2_i2c_new_subdev_board(&lw->v4l2_dev, adapt, - lw->pdata.encoder.info, NULL); - - i2c_put_adapter(adapt); - - if (!lw->sd_enc) { - dev_err(&vdev->dev, "Failed to get encoder: %s\n", - lw->pdata.encoder.module_name); - err = -ENODEV; - goto out; - } - } - - fh = kzalloc(sizeof(*fh), GFP_KERNEL); - if (!fh) { - err = -ENOMEM; - goto out; - } - - fh->cur_norm = timblogiw_tvnorms; - timblogiw_querystd(file, fh, &std); - fh->cur_norm = timblogiw_get_norm(std); - - INIT_LIST_HEAD(&fh->capture); - spin_lock_init(&fh->queue_lock); - - dma_cap_zero(mask); - dma_cap_set(DMA_SLAVE, mask); - dma_cap_set(DMA_PRIVATE, mask); - - /* find the DMA channel */ - fh->chan = dma_request_channel(mask, timblogiw_dma_filter_fn, - (void *)(uintptr_t)lw->pdata.dma_channel); - if (!fh->chan) { - dev_err(&vdev->dev, "Failed to get DMA channel\n"); - kfree(fh); - err = -ENODEV; - goto out; - } - - file->private_data = fh; - videobuf_queue_dma_contig_init(&fh->vb_vidq, - &timblogiw_video_qops, lw->dev, &fh->queue_lock, - V4L2_BUF_TYPE_VIDEO_CAPTURE, V4L2_FIELD_NONE, - sizeof(struct timblogiw_buffer), fh, NULL); - - lw->opened = true; -out: - mutex_unlock(&lw->lock); - - return err; -} - -static int timblogiw_close(struct file *file) -{ - struct video_device *vdev = video_devdata(file); - struct timblogiw *lw = video_get_drvdata(vdev); - struct timblogiw_fh *fh = file->private_data; - - dev_dbg(&vdev->dev, "%s: Entry\n", __func__); - - videobuf_stop(&fh->vb_vidq); - videobuf_mmap_free(&fh->vb_vidq); - - dma_release_channel(fh->chan); - - kfree(fh); - - mutex_lock(&lw->lock); - lw->opened = false; - mutex_unlock(&lw->lock); - return 0; -} - -static ssize_t timblogiw_read(struct file *file, char __user *data, - size_t count, loff_t *ppos) -{ - struct video_device *vdev = video_devdata(file); - struct timblogiw_fh *fh = file->private_data; - - dev_dbg(&vdev->dev, "%s: entry\n", __func__); - - return videobuf_read_stream(&fh->vb_vidq, data, count, ppos, 0, - file->f_flags & O_NONBLOCK); -} - -static unsigned int timblogiw_poll(struct file *file, - struct poll_table_struct *wait) -{ - struct video_device *vdev = video_devdata(file); - struct timblogiw_fh *fh = file->private_data; - - dev_dbg(&vdev->dev, "%s: entry\n", __func__); - - return videobuf_poll_stream(file, &fh->vb_vidq, wait); -} - -static int timblogiw_mmap(struct file *file, struct vm_area_struct *vma) -{ - struct video_device *vdev = video_devdata(file); - struct timblogiw_fh *fh = file->private_data; - - dev_dbg(&vdev->dev, "%s: entry\n", __func__); - - return videobuf_mmap_mapper(&fh->vb_vidq, vma); -} - -/* Platform device functions */ - -static struct v4l2_ioctl_ops timblogiw_ioctl_ops = { - .vidioc_querycap = timblogiw_querycap, - .vidioc_enum_fmt_vid_cap = timblogiw_enum_fmt, - .vidioc_g_fmt_vid_cap = timblogiw_g_fmt, - .vidioc_try_fmt_vid_cap = timblogiw_try_fmt, - .vidioc_s_fmt_vid_cap = timblogiw_s_fmt, - .vidioc_g_parm = timblogiw_g_parm, - .vidioc_reqbufs = timblogiw_reqbufs, - .vidioc_querybuf = timblogiw_querybuf, - .vidioc_qbuf = timblogiw_qbuf, - .vidioc_dqbuf = timblogiw_dqbuf, - .vidioc_g_std = timblogiw_g_std, - .vidioc_s_std = timblogiw_s_std, - .vidioc_enum_input = timblogiw_enuminput, - .vidioc_g_input = timblogiw_g_input, - .vidioc_s_input = timblogiw_s_input, - .vidioc_streamon = timblogiw_streamon, - .vidioc_streamoff = timblogiw_streamoff, - .vidioc_querystd = timblogiw_querystd, - .vidioc_enum_framesizes = timblogiw_enum_framesizes, -}; - -static struct v4l2_file_operations timblogiw_fops = { - .owner = THIS_MODULE, - .open = timblogiw_open, - .release = timblogiw_close, - .unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */ - .mmap = timblogiw_mmap, - .read = timblogiw_read, - .poll = timblogiw_poll, -}; - -static struct video_device timblogiw_template = { - .name = TIMBLOGIWIN_NAME, - .fops = &timblogiw_fops, - .ioctl_ops = &timblogiw_ioctl_ops, - .release = video_device_release_empty, - .minor = -1, - .tvnorms = V4L2_STD_PAL | V4L2_STD_NTSC -}; - -static int timblogiw_probe(struct platform_device *pdev) -{ - int err; - struct timblogiw *lw = NULL; - struct timb_video_platform_data *pdata = pdev->dev.platform_data; - - if (!pdata) { - dev_err(&pdev->dev, "No platform data\n"); - err = -EINVAL; - goto err; - } - - if (!pdata->encoder.module_name) - dev_info(&pdev->dev, "Running without decoder\n"); - - lw = devm_kzalloc(&pdev->dev, sizeof(*lw), GFP_KERNEL); - if (!lw) { - err = -ENOMEM; - goto err; - } - - if (pdev->dev.parent) - lw->dev = pdev->dev.parent; - else - lw->dev = &pdev->dev; - - memcpy(&lw->pdata, pdata, sizeof(lw->pdata)); - - mutex_init(&lw->lock); - - lw->video_dev = timblogiw_template; - - strlcpy(lw->v4l2_dev.name, DRIVER_NAME, sizeof(lw->v4l2_dev.name)); - err = v4l2_device_register(NULL, &lw->v4l2_dev); - if (err) - goto err; - - lw->video_dev.v4l2_dev = &lw->v4l2_dev; - - platform_set_drvdata(pdev, lw); - video_set_drvdata(&lw->video_dev, lw); - - err = video_register_device(&lw->video_dev, VFL_TYPE_GRABBER, 0); - if (err) { - dev_err(&pdev->dev, "Error reg video: %d\n", err); - goto err_request; - } - - return 0; - -err_request: - v4l2_device_unregister(&lw->v4l2_dev); -err: - dev_err(&pdev->dev, "Failed to register: %d\n", err); - - return err; -} - -static int timblogiw_remove(struct platform_device *pdev) -{ - struct timblogiw *lw = platform_get_drvdata(pdev); - - video_unregister_device(&lw->video_dev); - - v4l2_device_unregister(&lw->v4l2_dev); - - return 0; -} - -static struct platform_driver timblogiw_platform_driver = { - .driver = { - .name = DRIVER_NAME, - }, - .probe = timblogiw_probe, - .remove = timblogiw_remove, -}; - -module_platform_driver(timblogiw_platform_driver); - -MODULE_DESCRIPTION(TIMBLOGIWIN_NAME); -MODULE_AUTHOR("Pelagicore AB <info@pelagicore.com>"); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:"DRIVER_NAME); |