|  | /* | 
|  | * adv7180.c Analog Devices ADV7180 video decoder driver | 
|  | * Copyright (c) 2009 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. | 
|  | */ | 
|  |  | 
|  | #include <linux/module.h> | 
|  | #include <linux/init.h> | 
|  | #include <linux/errno.h> | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/interrupt.h> | 
|  | #include <linux/i2c.h> | 
|  | #include <linux/slab.h> | 
|  | #include <media/v4l2-ioctl.h> | 
|  | #include <linux/videodev2.h> | 
|  | #include <media/v4l2-device.h> | 
|  | #include <media/v4l2-chip-ident.h> | 
|  | #include <linux/mutex.h> | 
|  |  | 
|  | #define DRIVER_NAME "adv7180" | 
|  |  | 
|  | #define ADV7180_INPUT_CONTROL_REG			0x00 | 
|  | #define ADV7180_INPUT_CONTROL_AD_PAL_BG_NTSC_J_SECAM	0x00 | 
|  | #define ADV7180_INPUT_CONTROL_AD_PAL_BG_NTSC_J_SECAM_PED 0x10 | 
|  | #define ADV7180_INPUT_CONTROL_AD_PAL_N_NTSC_J_SECAM	0x20 | 
|  | #define ADV7180_INPUT_CONTROL_AD_PAL_N_NTSC_M_SECAM	0x30 | 
|  | #define ADV7180_INPUT_CONTROL_NTSC_J			0x40 | 
|  | #define ADV7180_INPUT_CONTROL_NTSC_M			0x50 | 
|  | #define ADV7180_INPUT_CONTROL_PAL60			0x60 | 
|  | #define ADV7180_INPUT_CONTROL_NTSC_443			0x70 | 
|  | #define ADV7180_INPUT_CONTROL_PAL_BG			0x80 | 
|  | #define ADV7180_INPUT_CONTROL_PAL_N			0x90 | 
|  | #define ADV7180_INPUT_CONTROL_PAL_M			0xa0 | 
|  | #define ADV7180_INPUT_CONTROL_PAL_M_PED			0xb0 | 
|  | #define ADV7180_INPUT_CONTROL_PAL_COMB_N		0xc0 | 
|  | #define ADV7180_INPUT_CONTROL_PAL_COMB_N_PED		0xd0 | 
|  | #define ADV7180_INPUT_CONTROL_PAL_SECAM			0xe0 | 
|  | #define ADV7180_INPUT_CONTROL_PAL_SECAM_PED		0xf0 | 
|  |  | 
|  | #define ADV7180_EXTENDED_OUTPUT_CONTROL_REG		0x04 | 
|  | #define ADV7180_EXTENDED_OUTPUT_CONTROL_NTSCDIS		0xC5 | 
|  |  | 
|  | #define ADV7180_AUTODETECT_ENABLE_REG			0x07 | 
|  | #define ADV7180_AUTODETECT_DEFAULT			0x7f | 
|  |  | 
|  | #define ADV7180_ADI_CTRL_REG				0x0e | 
|  | #define ADV7180_ADI_CTRL_IRQ_SPACE			0x20 | 
|  |  | 
|  | #define ADV7180_STATUS1_REG				0x10 | 
|  | #define ADV7180_STATUS1_IN_LOCK		0x01 | 
|  | #define ADV7180_STATUS1_AUTOD_MASK	0x70 | 
|  | #define ADV7180_STATUS1_AUTOD_NTSM_M_J	0x00 | 
|  | #define ADV7180_STATUS1_AUTOD_NTSC_4_43 0x10 | 
|  | #define ADV7180_STATUS1_AUTOD_PAL_M	0x20 | 
|  | #define ADV7180_STATUS1_AUTOD_PAL_60	0x30 | 
|  | #define ADV7180_STATUS1_AUTOD_PAL_B_G	0x40 | 
|  | #define ADV7180_STATUS1_AUTOD_SECAM	0x50 | 
|  | #define ADV7180_STATUS1_AUTOD_PAL_COMB	0x60 | 
|  | #define ADV7180_STATUS1_AUTOD_SECAM_525	0x70 | 
|  |  | 
|  | #define ADV7180_IDENT_REG 0x11 | 
|  | #define ADV7180_ID_7180 0x18 | 
|  |  | 
|  | #define ADV7180_ICONF1_ADI		0x40 | 
|  | #define ADV7180_ICONF1_ACTIVE_LOW	0x01 | 
|  | #define ADV7180_ICONF1_PSYNC_ONLY	0x10 | 
|  | #define ADV7180_ICONF1_ACTIVE_TO_CLR	0xC0 | 
|  |  | 
|  | #define ADV7180_IRQ1_LOCK	0x01 | 
|  | #define ADV7180_IRQ1_UNLOCK	0x02 | 
|  | #define ADV7180_ISR1_ADI	0x42 | 
|  | #define ADV7180_ICR1_ADI	0x43 | 
|  | #define ADV7180_IMR1_ADI	0x44 | 
|  | #define ADV7180_IMR2_ADI	0x48 | 
|  | #define ADV7180_IRQ3_AD_CHANGE	0x08 | 
|  | #define ADV7180_ISR3_ADI	0x4A | 
|  | #define ADV7180_ICR3_ADI	0x4B | 
|  | #define ADV7180_IMR3_ADI	0x4C | 
|  | #define ADV7180_IMR4_ADI	0x50 | 
|  |  | 
|  | struct adv7180_state { | 
|  | struct v4l2_subdev	sd; | 
|  | struct work_struct	work; | 
|  | struct mutex		mutex; /* mutual excl. when accessing chip */ | 
|  | int			irq; | 
|  | v4l2_std_id		curr_norm; | 
|  | bool			autodetect; | 
|  | }; | 
|  |  | 
|  | static v4l2_std_id adv7180_std_to_v4l2(u8 status1) | 
|  | { | 
|  | switch (status1 & ADV7180_STATUS1_AUTOD_MASK) { | 
|  | case ADV7180_STATUS1_AUTOD_NTSM_M_J: | 
|  | return V4L2_STD_NTSC; | 
|  | case ADV7180_STATUS1_AUTOD_NTSC_4_43: | 
|  | return V4L2_STD_NTSC_443; | 
|  | case ADV7180_STATUS1_AUTOD_PAL_M: | 
|  | return V4L2_STD_PAL_M; | 
|  | case ADV7180_STATUS1_AUTOD_PAL_60: | 
|  | return V4L2_STD_PAL_60; | 
|  | case ADV7180_STATUS1_AUTOD_PAL_B_G: | 
|  | return V4L2_STD_PAL; | 
|  | case ADV7180_STATUS1_AUTOD_SECAM: | 
|  | return V4L2_STD_SECAM; | 
|  | case ADV7180_STATUS1_AUTOD_PAL_COMB: | 
|  | return V4L2_STD_PAL_Nc | V4L2_STD_PAL_N; | 
|  | case ADV7180_STATUS1_AUTOD_SECAM_525: | 
|  | return V4L2_STD_SECAM; | 
|  | default: | 
|  | return V4L2_STD_UNKNOWN; | 
|  | } | 
|  | } | 
|  |  | 
|  | static int v4l2_std_to_adv7180(v4l2_std_id std) | 
|  | { | 
|  | if (std == V4L2_STD_PAL_60) | 
|  | return ADV7180_INPUT_CONTROL_PAL60; | 
|  | if (std == V4L2_STD_NTSC_443) | 
|  | return ADV7180_INPUT_CONTROL_NTSC_443; | 
|  | if (std == V4L2_STD_PAL_N) | 
|  | return ADV7180_INPUT_CONTROL_PAL_N; | 
|  | if (std == V4L2_STD_PAL_M) | 
|  | return ADV7180_INPUT_CONTROL_PAL_M; | 
|  | if (std == V4L2_STD_PAL_Nc) | 
|  | return ADV7180_INPUT_CONTROL_PAL_COMB_N; | 
|  |  | 
|  | if (std & V4L2_STD_PAL) | 
|  | return ADV7180_INPUT_CONTROL_PAL_BG; | 
|  | if (std & V4L2_STD_NTSC) | 
|  | return ADV7180_INPUT_CONTROL_NTSC_M; | 
|  | if (std & V4L2_STD_SECAM) | 
|  | return ADV7180_INPUT_CONTROL_PAL_SECAM; | 
|  |  | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | static u32 adv7180_status_to_v4l2(u8 status1) | 
|  | { | 
|  | if (!(status1 & ADV7180_STATUS1_IN_LOCK)) | 
|  | return V4L2_IN_ST_NO_SIGNAL; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int __adv7180_status(struct i2c_client *client, u32 *status, | 
|  | v4l2_std_id *std) | 
|  | { | 
|  | int status1 = i2c_smbus_read_byte_data(client, ADV7180_STATUS1_REG); | 
|  |  | 
|  | if (status1 < 0) | 
|  | return status1; | 
|  |  | 
|  | if (status) | 
|  | *status = adv7180_status_to_v4l2(status1); | 
|  | if (std) | 
|  | *std = adv7180_std_to_v4l2(status1); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static inline struct adv7180_state *to_state(struct v4l2_subdev *sd) | 
|  | { | 
|  | return container_of(sd, struct adv7180_state, sd); | 
|  | } | 
|  |  | 
|  | static int adv7180_querystd(struct v4l2_subdev *sd, v4l2_std_id *std) | 
|  | { | 
|  | struct adv7180_state *state = to_state(sd); | 
|  | int err = mutex_lock_interruptible(&state->mutex); | 
|  | if (err) | 
|  | return err; | 
|  |  | 
|  | /* when we are interrupt driven we know the state */ | 
|  | if (!state->autodetect || state->irq > 0) | 
|  | *std = state->curr_norm; | 
|  | else | 
|  | err = __adv7180_status(v4l2_get_subdevdata(sd), NULL, std); | 
|  |  | 
|  | mutex_unlock(&state->mutex); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static int adv7180_g_input_status(struct v4l2_subdev *sd, u32 *status) | 
|  | { | 
|  | struct adv7180_state *state = to_state(sd); | 
|  | int ret = mutex_lock_interruptible(&state->mutex); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | ret = __adv7180_status(v4l2_get_subdevdata(sd), status, NULL); | 
|  | mutex_unlock(&state->mutex); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int adv7180_g_chip_ident(struct v4l2_subdev *sd, | 
|  | struct v4l2_dbg_chip_ident *chip) | 
|  | { | 
|  | struct i2c_client *client = v4l2_get_subdevdata(sd); | 
|  |  | 
|  | return v4l2_chip_ident_i2c_client(client, chip, V4L2_IDENT_ADV7180, 0); | 
|  | } | 
|  |  | 
|  | static int adv7180_s_std(struct v4l2_subdev *sd, v4l2_std_id std) | 
|  | { | 
|  | struct adv7180_state *state = to_state(sd); | 
|  | struct i2c_client *client = v4l2_get_subdevdata(sd); | 
|  | int ret = mutex_lock_interruptible(&state->mutex); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | /* all standards -> autodetect */ | 
|  | if (std == V4L2_STD_ALL) { | 
|  | ret = i2c_smbus_write_byte_data(client, | 
|  | ADV7180_INPUT_CONTROL_REG, | 
|  | ADV7180_INPUT_CONTROL_AD_PAL_BG_NTSC_J_SECAM); | 
|  | if (ret < 0) | 
|  | goto out; | 
|  |  | 
|  | __adv7180_status(client, NULL, &state->curr_norm); | 
|  | state->autodetect = true; | 
|  | } else { | 
|  | ret = v4l2_std_to_adv7180(std); | 
|  | if (ret < 0) | 
|  | goto out; | 
|  |  | 
|  | ret = i2c_smbus_write_byte_data(client, | 
|  | ADV7180_INPUT_CONTROL_REG, ret); | 
|  | if (ret < 0) | 
|  | goto out; | 
|  |  | 
|  | state->curr_norm = std; | 
|  | state->autodetect = false; | 
|  | } | 
|  | ret = 0; | 
|  | out: | 
|  | mutex_unlock(&state->mutex); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static const struct v4l2_subdev_video_ops adv7180_video_ops = { | 
|  | .querystd = adv7180_querystd, | 
|  | .g_input_status = adv7180_g_input_status, | 
|  | }; | 
|  |  | 
|  | static const struct v4l2_subdev_core_ops adv7180_core_ops = { | 
|  | .g_chip_ident = adv7180_g_chip_ident, | 
|  | .s_std = adv7180_s_std, | 
|  | }; | 
|  |  | 
|  | static const struct v4l2_subdev_ops adv7180_ops = { | 
|  | .core = &adv7180_core_ops, | 
|  | .video = &adv7180_video_ops, | 
|  | }; | 
|  |  | 
|  | static void adv7180_work(struct work_struct *work) | 
|  | { | 
|  | struct adv7180_state *state = container_of(work, struct adv7180_state, | 
|  | work); | 
|  | struct i2c_client *client = v4l2_get_subdevdata(&state->sd); | 
|  | u8 isr3; | 
|  |  | 
|  | mutex_lock(&state->mutex); | 
|  | i2c_smbus_write_byte_data(client, ADV7180_ADI_CTRL_REG, | 
|  | ADV7180_ADI_CTRL_IRQ_SPACE); | 
|  | isr3 = i2c_smbus_read_byte_data(client, ADV7180_ISR3_ADI); | 
|  | /* clear */ | 
|  | i2c_smbus_write_byte_data(client, ADV7180_ICR3_ADI, isr3); | 
|  | i2c_smbus_write_byte_data(client, ADV7180_ADI_CTRL_REG, 0); | 
|  |  | 
|  | if (isr3 & ADV7180_IRQ3_AD_CHANGE && state->autodetect) | 
|  | __adv7180_status(client, NULL, &state->curr_norm); | 
|  | mutex_unlock(&state->mutex); | 
|  |  | 
|  | enable_irq(state->irq); | 
|  | } | 
|  |  | 
|  | static irqreturn_t adv7180_irq(int irq, void *devid) | 
|  | { | 
|  | struct adv7180_state *state = devid; | 
|  |  | 
|  | schedule_work(&state->work); | 
|  |  | 
|  | disable_irq_nosync(state->irq); | 
|  |  | 
|  | return IRQ_HANDLED; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Generic i2c probe | 
|  | * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' | 
|  | */ | 
|  |  | 
|  | static __devinit int adv7180_probe(struct i2c_client *client, | 
|  | const struct i2c_device_id *id) | 
|  | { | 
|  | struct adv7180_state *state; | 
|  | struct v4l2_subdev *sd; | 
|  | int ret; | 
|  |  | 
|  | /* Check if the adapter supports the needed features */ | 
|  | if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) | 
|  | return -EIO; | 
|  |  | 
|  | v4l_info(client, "chip found @ 0x%02x (%s)\n", | 
|  | client->addr << 1, client->adapter->name); | 
|  |  | 
|  | state = kzalloc(sizeof(struct adv7180_state), GFP_KERNEL); | 
|  | if (state == NULL) { | 
|  | ret = -ENOMEM; | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | state->irq = client->irq; | 
|  | INIT_WORK(&state->work, adv7180_work); | 
|  | mutex_init(&state->mutex); | 
|  | state->autodetect = true; | 
|  | sd = &state->sd; | 
|  | v4l2_i2c_subdev_init(sd, client, &adv7180_ops); | 
|  |  | 
|  | /* Initialize adv7180 */ | 
|  | /* Enable autodetection */ | 
|  | ret = i2c_smbus_write_byte_data(client, ADV7180_INPUT_CONTROL_REG, | 
|  | ADV7180_INPUT_CONTROL_AD_PAL_BG_NTSC_J_SECAM); | 
|  | if (ret < 0) | 
|  | goto err_unreg_subdev; | 
|  |  | 
|  | ret = i2c_smbus_write_byte_data(client, ADV7180_AUTODETECT_ENABLE_REG, | 
|  | ADV7180_AUTODETECT_DEFAULT); | 
|  | if (ret < 0) | 
|  | goto err_unreg_subdev; | 
|  |  | 
|  | /* ITU-R BT.656-4 compatible */ | 
|  | ret = i2c_smbus_write_byte_data(client, | 
|  | ADV7180_EXTENDED_OUTPUT_CONTROL_REG, | 
|  | ADV7180_EXTENDED_OUTPUT_CONTROL_NTSCDIS); | 
|  | if (ret < 0) | 
|  | goto err_unreg_subdev; | 
|  |  | 
|  | /* read current norm */ | 
|  | __adv7180_status(client, NULL, &state->curr_norm); | 
|  |  | 
|  | /* register for interrupts */ | 
|  | if (state->irq > 0) { | 
|  | ret = request_irq(state->irq, adv7180_irq, 0, DRIVER_NAME, | 
|  | state); | 
|  | if (ret) | 
|  | goto err_unreg_subdev; | 
|  |  | 
|  | ret = i2c_smbus_write_byte_data(client, ADV7180_ADI_CTRL_REG, | 
|  | ADV7180_ADI_CTRL_IRQ_SPACE); | 
|  | if (ret < 0) | 
|  | goto err_unreg_subdev; | 
|  |  | 
|  | /* config the Interrupt pin to be active low */ | 
|  | ret = i2c_smbus_write_byte_data(client, ADV7180_ICONF1_ADI, | 
|  | ADV7180_ICONF1_ACTIVE_LOW | ADV7180_ICONF1_PSYNC_ONLY); | 
|  | if (ret < 0) | 
|  | goto err_unreg_subdev; | 
|  |  | 
|  | ret = i2c_smbus_write_byte_data(client, ADV7180_IMR1_ADI, 0); | 
|  | if (ret < 0) | 
|  | goto err_unreg_subdev; | 
|  |  | 
|  | ret = i2c_smbus_write_byte_data(client, ADV7180_IMR2_ADI, 0); | 
|  | if (ret < 0) | 
|  | goto err_unreg_subdev; | 
|  |  | 
|  | /* enable AD change interrupts interrupts */ | 
|  | ret = i2c_smbus_write_byte_data(client, ADV7180_IMR3_ADI, | 
|  | ADV7180_IRQ3_AD_CHANGE); | 
|  | if (ret < 0) | 
|  | goto err_unreg_subdev; | 
|  |  | 
|  | ret = i2c_smbus_write_byte_data(client, ADV7180_IMR4_ADI, 0); | 
|  | if (ret < 0) | 
|  | goto err_unreg_subdev; | 
|  |  | 
|  | ret = i2c_smbus_write_byte_data(client, ADV7180_ADI_CTRL_REG, | 
|  | 0); | 
|  | if (ret < 0) | 
|  | goto err_unreg_subdev; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | err_unreg_subdev: | 
|  | mutex_destroy(&state->mutex); | 
|  | v4l2_device_unregister_subdev(sd); | 
|  | kfree(state); | 
|  | err: | 
|  | printk(KERN_ERR DRIVER_NAME ": Failed to probe: %d\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static __devexit int adv7180_remove(struct i2c_client *client) | 
|  | { | 
|  | struct v4l2_subdev *sd = i2c_get_clientdata(client); | 
|  | struct adv7180_state *state = to_state(sd); | 
|  |  | 
|  | if (state->irq > 0) { | 
|  | free_irq(client->irq, state); | 
|  | if (cancel_work_sync(&state->work)) { | 
|  | /* | 
|  | * Work was pending, therefore we need to enable | 
|  | * IRQ here to balance the disable_irq() done in the | 
|  | * interrupt handler. | 
|  | */ | 
|  | enable_irq(state->irq); | 
|  | } | 
|  | } | 
|  |  | 
|  | mutex_destroy(&state->mutex); | 
|  | v4l2_device_unregister_subdev(sd); | 
|  | kfree(to_state(sd)); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct i2c_device_id adv7180_id[] = { | 
|  | {DRIVER_NAME, 0}, | 
|  | {}, | 
|  | }; | 
|  |  | 
|  | MODULE_DEVICE_TABLE(i2c, adv7180_id); | 
|  |  | 
|  | static struct i2c_driver adv7180_driver = { | 
|  | .driver = { | 
|  | .owner	= THIS_MODULE, | 
|  | .name	= DRIVER_NAME, | 
|  | }, | 
|  | .probe		= adv7180_probe, | 
|  | .remove		= __devexit_p(adv7180_remove), | 
|  | .id_table	= adv7180_id, | 
|  | }; | 
|  |  | 
|  | static __init int adv7180_init(void) | 
|  | { | 
|  | return i2c_add_driver(&adv7180_driver); | 
|  | } | 
|  |  | 
|  | static __exit void adv7180_exit(void) | 
|  | { | 
|  | i2c_del_driver(&adv7180_driver); | 
|  | } | 
|  |  | 
|  | module_init(adv7180_init); | 
|  | module_exit(adv7180_exit); | 
|  |  | 
|  | MODULE_DESCRIPTION("Analog Devices ADV7180 video decoder driver"); | 
|  | MODULE_AUTHOR("Mocean Laboratories"); | 
|  | MODULE_LICENSE("GPL v2"); | 
|  |  |