mirror of
				git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
				synced 2025-10-31 08:44:41 +00:00 
			
		
		
		
	Char / misc driver patches for 3.16-rc1
Here is the big char / misc driver updates for 3.16-rc1. Lots of different driver updates for a variety of different drivers and minor driver subsystems. All have been in linux-next with no reported issues. Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> -----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.22 (GNU/Linux) iEYEABECAAYFAlONWI8ACgkQMUfUDdst+ykvQACdGxTChdEU7edElDAXeelVmu8v D1UAoLDvqwUsN7t5v+WG2wkOvhf5MEA7 =tVMP -----END PGP SIGNATURE----- Merge tag 'char-misc-3.16-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc into next Pull char/misc driver patches from Greg KH: "Here is the big char / misc driver update for 3.16-rc1. Lots of different driver updates for a variety of different drivers and minor driver subsystems. All have been in linux-next with no reported issues" * tag 'char-misc-3.16-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc: (79 commits) hv: use correct order when freeing monitor_pages spmi: of: fixup generic SPMI devicetree binding example applicom: dereferencing NULL on error path misc: genwqe: fix uninitialized return value in genwqe_free_sync_sgl() miscdevice.h: Simple syntax fix to make pointers consistent. MAINTAINERS: Add miscdevice.h to file list for char/misc drivers. mcb: Add support for shared PCI IRQs drivers: Remove duplicate conditionally included subdirs misc: atmel_pwm: only build for supported platforms mei: me: move probe quirk to cfg structure mei: add per device configuration mei: me: read H_CSR after asserting reset mei: me: drop harmful wait optimization mei: me: fix hw ready reset flow mei: fix memory leak of mei_clients array uio: fix vma io range check in mmap drivers: uio_dmem_genirq: Fix memory leak in uio_dmem_genirq_probe() w1: do not unlock unheld list_mutex in __w1_remove_master_device() w1: optional bundling of netlink kernel replies connector: allow multiple messages to be sent in one packet ...
This commit is contained in:
		
						commit
						4046136afb
					
				
					 68 changed files with 2931 additions and 787 deletions
				
			
		|  | @ -24,7 +24,8 @@ netlink based networking for inter-process communication in a significantly | |||
| easier way: | ||||
| 
 | ||||
| int cn_add_callback(struct cb_id *id, char *name, void (*callback) (struct cn_msg *, struct netlink_skb_parms *)); | ||||
| void cn_netlink_send(struct cn_msg *msg, u32 __group, int gfp_mask); | ||||
| void cn_netlink_send_multi(struct cn_msg *msg, u16 len, u32 portid, u32 __group, int gfp_mask); | ||||
| void cn_netlink_send(struct cn_msg *msg, u32 portid, u32 __group, int gfp_mask); | ||||
| 
 | ||||
| struct cb_id | ||||
| { | ||||
|  | @ -71,15 +72,21 @@ void cn_del_callback(struct cb_id *id); | |||
|  struct cb_id *id		- unique connector's user identifier. | ||||
| 
 | ||||
| 
 | ||||
| int cn_netlink_send(struct cn_msg *msg, u32 __groups, int gfp_mask); | ||||
| int cn_netlink_send_multi(struct cn_msg *msg, u16 len, u32 portid, u32 __groups, int gfp_mask); | ||||
| int cn_netlink_send(struct cn_msg *msg, u32 portid, u32 __groups, int gfp_mask); | ||||
| 
 | ||||
|  Sends message to the specified groups.  It can be safely called from | ||||
|  softirq context, but may silently fail under strong memory pressure. | ||||
|  If there are no listeners for given group -ESRCH can be returned. | ||||
| 
 | ||||
|  struct cn_msg *		- message header(with attached data). | ||||
|  u16 len			- for *_multi multiple cn_msg messages can be sent | ||||
|  u32 port			- destination port. | ||||
|  				  If non-zero the message will be sent to the | ||||
| 				  given port, which should be set to the | ||||
| 				  original sender. | ||||
|  u32 __group			- destination group. | ||||
| 				  If __group is zero, then appropriate group will | ||||
| 				  If port and __group is zero, then appropriate group will | ||||
| 				  be searched through all registered connector users, | ||||
| 				  and message will be delivered to the group which was | ||||
| 				  created for user with the same ID as in msg. | ||||
|  | @ -111,7 +118,7 @@ acknowledge number MUST be the same + 1. | |||
| If we receive a message and its sequence number is not equal to one we | ||||
| are expecting, then it is a new message.  If we receive a message and | ||||
| its sequence number is the same as one we are expecting, but its | ||||
| acknowledge is not equal to the acknowledge number in the original | ||||
| acknowledge is not equal to the sequence number in the original | ||||
| message + 1, then it is a new message. | ||||
| 
 | ||||
| Obviously, the protocol header contains the above id. | ||||
|  |  | |||
							
								
								
									
										18
									
								
								Documentation/devicetree/bindings/misc/arm-charlcd.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								Documentation/devicetree/bindings/misc/arm-charlcd.txt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | |||
| ARM Versatile Character LCD | ||||
| ----------------------------------------------------- | ||||
| This binding defines the character LCD interface found on ARM Versatile AB | ||||
| and PB reference platforms. | ||||
| 
 | ||||
| Required properties: | ||||
| - compatible : "arm,versatile-clcd" | ||||
| - reg : Location and size of character LCD registers | ||||
| 
 | ||||
| Optional properties: | ||||
| - interrupts - single interrupt for character LCD. The character LCD can | ||||
|   operate in polled mode without an interrupt. | ||||
| 
 | ||||
| Example: | ||||
| 	lcd@10008000 { | ||||
| 		compatible = "arm,versatile-lcd"; | ||||
| 		reg = <0x10008000 0x1000>; | ||||
| 	}; | ||||
|  | @ -26,7 +26,7 @@ Each child node must have one and only one 'reg' entry of type SPMI_USID. | |||
| 		reg = <...>; | ||||
| 
 | ||||
| 		#address-cells = <2>; | ||||
| 		#size-cells <0>; | ||||
| 		#size-cells = <0>; | ||||
| 
 | ||||
| 		child@0 { | ||||
| 			compatible = "..."; | ||||
|  |  | |||
|  | @ -82,7 +82,7 @@ driver             - (standard) symlink to the w1 driver | |||
| w1_master_add      - Manually register a slave device | ||||
| w1_master_attempts - the number of times a search was attempted | ||||
| w1_master_max_slave_count | ||||
|                    - the maximum slaves that may be attached to a master | ||||
|                    - maximum number of slaves to search for at a time | ||||
| w1_master_name     - the name of the device (w1_bus_masterX) | ||||
| w1_master_pullup   - 5V strong pullup 0 enabled, 1 disabled | ||||
| w1_master_remove   - Manually remove a slave device | ||||
|  |  | |||
|  | @ -30,7 +30,7 @@ Protocol. | |||
| 			W1_SLAVE_CMD | ||||
| 				userspace command for slave device | ||||
| 				(read/write/touch) | ||||
| 	__u8 res	- reserved | ||||
| 	__u8 status	- error indication from kernel | ||||
| 	__u16 len	- size of data attached to this header data | ||||
| 	union { | ||||
| 		__u8 id[8];			 - slave unique device id | ||||
|  | @ -44,10 +44,14 @@ Protocol. | |||
| 	__u8 cmd	- command opcode. | ||||
| 			W1_CMD_READ 	- read command | ||||
| 			W1_CMD_WRITE	- write command | ||||
| 			W1_CMD_TOUCH	- touch command | ||||
| 				(write and sample data back to userspace) | ||||
| 			W1_CMD_SEARCH	- search command | ||||
| 			W1_CMD_ALARM_SEARCH - alarm search command | ||||
| 			W1_CMD_TOUCH	- touch command | ||||
| 				(write and sample data back to userspace) | ||||
| 			W1_CMD_RESET	- send bus reset | ||||
| 			W1_CMD_SLAVE_ADD	- add slave to kernel list | ||||
| 			W1_CMD_SLAVE_REMOVE	- remove slave from kernel list | ||||
| 			W1_CMD_LIST_SLAVES	- get slaves list from kernel | ||||
| 	__u8 res	- reserved | ||||
| 	__u16 len	- length of data for this command | ||||
| 		For read command data must be allocated like for write command | ||||
|  | @ -87,8 +91,7 @@ format: | |||
| 	id0 ... idN | ||||
| 
 | ||||
| 	Each message is at most 4k in size, so if number of master devices | ||||
| 	exceeds this, it will be split into several messages, | ||||
| 	cn.seq will be increased for each one. | ||||
| 	exceeds this, it will be split into several messages. | ||||
| 
 | ||||
| W1 search and alarm search commands. | ||||
| request: | ||||
|  |  | |||
|  | @ -2188,6 +2188,7 @@ T:	git git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc.git | |||
| S:	Supported | ||||
| F:	drivers/char/* | ||||
| F:	drivers/misc/* | ||||
| F:	include/linux/miscdevice.h | ||||
| 
 | ||||
| CHECKPATCH | ||||
| M:	Andy Whitcroft <apw@canonical.com> | ||||
|  |  | |||
|  | @ -83,7 +83,6 @@ obj-$(CONFIG_PCCARD)		+= pcmcia/ | |||
| obj-$(CONFIG_DIO)		+= dio/ | ||||
| obj-$(CONFIG_SBUS)		+= sbus/ | ||||
| obj-$(CONFIG_ZORRO)		+= zorro/ | ||||
| obj-$(CONFIG_MAC)		+= macintosh/ | ||||
| obj-$(CONFIG_ATA_OVER_ETH)	+= block/aoe/ | ||||
| obj-$(CONFIG_PARIDE) 		+= block/paride/ | ||||
| obj-$(CONFIG_TC)		+= tc/ | ||||
|  | @ -141,7 +140,6 @@ obj-y				+= clk/ | |||
| 
 | ||||
| obj-$(CONFIG_MAILBOX)		+= mailbox/ | ||||
| obj-$(CONFIG_HWSPINLOCK)	+= hwspinlock/ | ||||
| obj-$(CONFIG_NFC)		+= nfc/ | ||||
| obj-$(CONFIG_IOMMU_SUPPORT)	+= iommu/ | ||||
| obj-$(CONFIG_REMOTEPROC)	+= remoteproc/ | ||||
| obj-$(CONFIG_RPMSG)		+= rpmsg/ | ||||
|  |  | |||
|  | @ -345,7 +345,6 @@ out: | |||
| 			free_irq(apbs[i].irq, &dummy); | ||||
| 		iounmap(apbs[i].RamIO); | ||||
| 	} | ||||
| 	pci_disable_device(dev); | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -43,6 +43,8 @@ static struct cn_dev cdev; | |||
| static int cn_already_initialized; | ||||
| 
 | ||||
| /*
 | ||||
|  * Sends mult (multiple) cn_msg at a time. | ||||
|  * | ||||
|  * msg->seq and msg->ack are used to determine message genealogy. | ||||
|  * When someone sends message it puts there locally unique sequence | ||||
|  * and random acknowledge numbers.  Sequence number may be copied into | ||||
|  | @ -62,10 +64,13 @@ static int cn_already_initialized; | |||
|  * the acknowledgement number in the original message + 1, then it is | ||||
|  * a new message. | ||||
|  * | ||||
|  * If msg->len != len, then additional cn_msg messages are expected following | ||||
|  * the first msg. | ||||
|  * | ||||
|  * The message is sent to, the portid if given, the group if given, both if | ||||
|  * both, or if both are zero then the group is looked up and sent there. | ||||
|  */ | ||||
| int cn_netlink_send(struct cn_msg *msg, u32 portid, u32 __group, | ||||
| int cn_netlink_send_mult(struct cn_msg *msg, u16 len, u32 portid, u32 __group, | ||||
| 	gfp_t gfp_mask) | ||||
| { | ||||
| 	struct cn_callback_entry *__cbq; | ||||
|  | @ -98,7 +103,7 @@ int cn_netlink_send(struct cn_msg *msg, u32 portid, u32 __group, | |||
| 	if (!portid && !netlink_has_listeners(dev->nls, group)) | ||||
| 		return -ESRCH; | ||||
| 
 | ||||
| 	size = sizeof(*msg) + msg->len; | ||||
| 	size = sizeof(*msg) + len; | ||||
| 
 | ||||
| 	skb = nlmsg_new(size, gfp_mask); | ||||
| 	if (!skb) | ||||
|  | @ -121,6 +126,14 @@ int cn_netlink_send(struct cn_msg *msg, u32 portid, u32 __group, | |||
| 					 gfp_mask); | ||||
| 	return netlink_unicast(dev->nls, skb, portid, !(gfp_mask&__GFP_WAIT)); | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(cn_netlink_send_mult); | ||||
| 
 | ||||
| /* same as cn_netlink_send_mult except msg->len is used for len */ | ||||
| int cn_netlink_send(struct cn_msg *msg, u32 portid, u32 __group, | ||||
| 	gfp_t gfp_mask) | ||||
| { | ||||
| 	return cn_netlink_send_mult(msg, msg->len, portid, __group, gfp_mask); | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(cn_netlink_send); | ||||
| 
 | ||||
| /*
 | ||||
|  |  | |||
|  | @ -28,13 +28,13 @@ config EXTCON_ADC_JACK | |||
| 	  Say Y here to enable extcon device driver based on ADC values. | ||||
| 
 | ||||
| config EXTCON_MAX14577 | ||||
| 	tristate "MAX14577 EXTCON Support" | ||||
| 	tristate "MAX14577/77836 EXTCON Support" | ||||
| 	depends on MFD_MAX14577 | ||||
| 	select IRQ_DOMAIN | ||||
| 	select REGMAP_I2C | ||||
| 	help | ||||
| 	  If you say yes here you get support for the MUIC device of | ||||
| 	  Maxim MAX14577 PMIC. The MAX14577 MUIC is a USB port accessory | ||||
| 	  Maxim MAX14577/77836. The MAX14577/77836 MUIC is a USB port accessory | ||||
| 	  detector and switch. | ||||
| 
 | ||||
| config EXTCON_MAX77693 | ||||
|  |  | |||
|  | @ -39,7 +39,7 @@ | |||
|  * @chan:		iio channel being queried. | ||||
|  */ | ||||
| struct adc_jack_data { | ||||
| 	struct extcon_dev edev; | ||||
| 	struct extcon_dev *edev; | ||||
| 
 | ||||
| 	const char **cable_names; | ||||
| 	int num_cables; | ||||
|  | @ -64,7 +64,7 @@ static void adc_jack_handler(struct work_struct *work) | |||
| 
 | ||||
| 	ret = iio_read_channel_raw(data->chan, &adc_val); | ||||
| 	if (ret < 0) { | ||||
| 		dev_err(&data->edev.dev, "read channel() error: %d\n", ret); | ||||
| 		dev_err(&data->edev->dev, "read channel() error: %d\n", ret); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
|  | @ -80,7 +80,7 @@ static void adc_jack_handler(struct work_struct *work) | |||
| 	} | ||||
| 	/* if no def has met, it means state = 0 (no cables attached) */ | ||||
| 
 | ||||
| 	extcon_set_state(&data->edev, state); | ||||
| 	extcon_set_state(data->edev, state); | ||||
| } | ||||
| 
 | ||||
| static irqreturn_t adc_jack_irq_thread(int irq, void *_data) | ||||
|  | @ -102,33 +102,33 @@ static int adc_jack_probe(struct platform_device *pdev) | |||
| 	if (!data) | ||||
| 		return -ENOMEM; | ||||
| 
 | ||||
| 	data->edev.name = pdata->name; | ||||
| 
 | ||||
| 	if (!pdata->cable_names) { | ||||
| 		err = -EINVAL; | ||||
| 		dev_err(&pdev->dev, "error: cable_names not defined.\n"); | ||||
| 		goto out; | ||||
| 		return -EINVAL; | ||||
| 	} | ||||
| 
 | ||||
| 	data->edev.dev.parent = &pdev->dev; | ||||
| 	data->edev.supported_cable = pdata->cable_names; | ||||
| 	data->edev = devm_extcon_dev_allocate(&pdev->dev, pdata->cable_names); | ||||
| 	if (IS_ERR(data->edev)) { | ||||
| 		dev_err(&pdev->dev, "failed to allocate extcon device\n"); | ||||
| 		return -ENOMEM; | ||||
| 	} | ||||
| 	data->edev->dev.parent = &pdev->dev; | ||||
| 	data->edev->name = pdata->name; | ||||
| 
 | ||||
| 	/* Check the length of array and set num_cables */ | ||||
| 	for (i = 0; data->edev.supported_cable[i]; i++) | ||||
| 	for (i = 0; data->edev->supported_cable[i]; i++) | ||||
| 		; | ||||
| 	if (i == 0 || i > SUPPORTED_CABLE_MAX) { | ||||
| 		err = -EINVAL; | ||||
| 		dev_err(&pdev->dev, "error: pdata->cable_names size = %d\n", | ||||
| 				i - 1); | ||||
| 		goto out; | ||||
| 		return -EINVAL; | ||||
| 	} | ||||
| 	data->num_cables = i; | ||||
| 
 | ||||
| 	if (!pdata->adc_conditions || | ||||
| 			!pdata->adc_conditions[0].state) { | ||||
| 		err = -EINVAL; | ||||
| 		dev_err(&pdev->dev, "error: adc_conditions not defined.\n"); | ||||
| 		goto out; | ||||
| 		return -EINVAL; | ||||
| 	} | ||||
| 	data->adc_conditions = pdata->adc_conditions; | ||||
| 
 | ||||
|  | @ -138,10 +138,8 @@ static int adc_jack_probe(struct platform_device *pdev) | |||
| 	data->num_conditions = i; | ||||
| 
 | ||||
| 	data->chan = iio_channel_get(&pdev->dev, pdata->consumer_channel); | ||||
| 	if (IS_ERR(data->chan)) { | ||||
| 		err = PTR_ERR(data->chan); | ||||
| 		goto out; | ||||
| 	} | ||||
| 	if (IS_ERR(data->chan)) | ||||
| 		return PTR_ERR(data->chan); | ||||
| 
 | ||||
| 	data->handling_delay = msecs_to_jiffies(pdata->handling_delay_ms); | ||||
| 
 | ||||
|  | @ -149,15 +147,14 @@ static int adc_jack_probe(struct platform_device *pdev) | |||
| 
 | ||||
| 	platform_set_drvdata(pdev, data); | ||||
| 
 | ||||
| 	err = extcon_dev_register(&data->edev); | ||||
| 	err = devm_extcon_dev_register(&pdev->dev, data->edev); | ||||
| 	if (err) | ||||
| 		goto out; | ||||
| 		return err; | ||||
| 
 | ||||
| 	data->irq = platform_get_irq(pdev, 0); | ||||
| 	if (!data->irq) { | ||||
| 		dev_err(&pdev->dev, "platform_get_irq failed\n"); | ||||
| 		err = -ENODEV; | ||||
| 		goto err_irq; | ||||
| 		return -ENODEV; | ||||
| 	} | ||||
| 
 | ||||
| 	err = request_any_context_irq(data->irq, adc_jack_irq_thread, | ||||
|  | @ -165,15 +162,10 @@ static int adc_jack_probe(struct platform_device *pdev) | |||
| 
 | ||||
| 	if (err < 0) { | ||||
| 		dev_err(&pdev->dev, "error: irq %d\n", data->irq); | ||||
| 		goto err_irq; | ||||
| 		return err; | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| 
 | ||||
| err_irq: | ||||
| 	extcon_dev_unregister(&data->edev); | ||||
| out: | ||||
| 	return err; | ||||
| } | ||||
| 
 | ||||
| static int adc_jack_remove(struct platform_device *pdev) | ||||
|  | @ -182,7 +174,6 @@ static int adc_jack_remove(struct platform_device *pdev) | |||
| 
 | ||||
| 	free_irq(data->irq, data); | ||||
| 	cancel_work_sync(&data->handler.work); | ||||
| 	extcon_dev_unregister(&data->edev); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
|  |  | |||
|  | @ -91,7 +91,7 @@ struct arizona_extcon_info { | |||
| 
 | ||||
| 	int hpdet_ip; | ||||
| 
 | ||||
| 	struct extcon_dev edev; | ||||
| 	struct extcon_dev *edev; | ||||
| }; | ||||
| 
 | ||||
| static const struct arizona_micd_config micd_default_modes[] = { | ||||
|  | @ -546,7 +546,7 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data) | |||
| 	} | ||||
| 
 | ||||
| 	/* If the cable was removed while measuring ignore the result */ | ||||
| 	ret = extcon_get_cable_state_(&info->edev, ARIZONA_CABLE_MECHANICAL); | ||||
| 	ret = extcon_get_cable_state_(info->edev, ARIZONA_CABLE_MECHANICAL); | ||||
| 	if (ret < 0) { | ||||
| 		dev_err(arizona->dev, "Failed to check cable state: %d\n", | ||||
| 			ret); | ||||
|  | @ -581,7 +581,7 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data) | |||
| 	else | ||||
| 		report = ARIZONA_CABLE_HEADPHONE; | ||||
| 
 | ||||
| 	ret = extcon_set_cable_state_(&info->edev, report, true); | ||||
| 	ret = extcon_set_cable_state_(info->edev, report, true); | ||||
| 	if (ret != 0) | ||||
| 		dev_err(arizona->dev, "Failed to report HP/line: %d\n", | ||||
| 			ret); | ||||
|  | @ -664,7 +664,7 @@ err: | |||
| 			   ARIZONA_ACCDET_MODE_MASK, ARIZONA_ACCDET_MODE_MIC); | ||||
| 
 | ||||
| 	/* Just report headphone */ | ||||
| 	ret = extcon_update_state(&info->edev, | ||||
| 	ret = extcon_update_state(info->edev, | ||||
| 				  1 << ARIZONA_CABLE_HEADPHONE, | ||||
| 				  1 << ARIZONA_CABLE_HEADPHONE); | ||||
| 	if (ret != 0) | ||||
|  | @ -723,7 +723,7 @@ err: | |||
| 			   ARIZONA_ACCDET_MODE_MASK, ARIZONA_ACCDET_MODE_MIC); | ||||
| 
 | ||||
| 	/* Just report headphone */ | ||||
| 	ret = extcon_update_state(&info->edev, | ||||
| 	ret = extcon_update_state(info->edev, | ||||
| 				  1 << ARIZONA_CABLE_HEADPHONE, | ||||
| 				  1 << ARIZONA_CABLE_HEADPHONE); | ||||
| 	if (ret != 0) | ||||
|  | @ -764,7 +764,7 @@ static void arizona_micd_detect(struct work_struct *work) | |||
| 	mutex_lock(&info->lock); | ||||
| 
 | ||||
| 	/* If the cable was removed while measuring ignore the result */ | ||||
| 	ret = extcon_get_cable_state_(&info->edev, ARIZONA_CABLE_MECHANICAL); | ||||
| 	ret = extcon_get_cable_state_(info->edev, ARIZONA_CABLE_MECHANICAL); | ||||
| 	if (ret < 0) { | ||||
| 		dev_err(arizona->dev, "Failed to check cable state: %d\n", | ||||
| 				ret); | ||||
|  | @ -812,7 +812,7 @@ static void arizona_micd_detect(struct work_struct *work) | |||
| 	if (info->detecting && (val & ARIZONA_MICD_LVL_8)) { | ||||
| 		arizona_identify_headphone(info); | ||||
| 
 | ||||
| 		ret = extcon_update_state(&info->edev, | ||||
| 		ret = extcon_update_state(info->edev, | ||||
| 					  1 << ARIZONA_CABLE_MICROPHONE, | ||||
| 					  1 << ARIZONA_CABLE_MICROPHONE); | ||||
| 
 | ||||
|  | @ -999,7 +999,7 @@ static irqreturn_t arizona_jackdet(int irq, void *data) | |||
| 
 | ||||
| 	if (info->last_jackdet == present) { | ||||
| 		dev_dbg(arizona->dev, "Detected jack\n"); | ||||
| 		ret = extcon_set_cable_state_(&info->edev, | ||||
| 		ret = extcon_set_cable_state_(info->edev, | ||||
| 					      ARIZONA_CABLE_MECHANICAL, true); | ||||
| 
 | ||||
| 		if (ret != 0) | ||||
|  | @ -1038,7 +1038,7 @@ static irqreturn_t arizona_jackdet(int irq, void *data) | |||
| 					 info->micd_ranges[i].key, 0); | ||||
| 		input_sync(info->input); | ||||
| 
 | ||||
| 		ret = extcon_update_state(&info->edev, 0xffffffff, 0); | ||||
| 		ret = extcon_update_state(info->edev, 0xffffffff, 0); | ||||
| 		if (ret != 0) | ||||
| 			dev_err(arizona->dev, "Removal report failed: %d\n", | ||||
| 				ret); | ||||
|  | @ -1105,15 +1105,14 @@ static int arizona_extcon_probe(struct platform_device *pdev) | |||
| 	info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); | ||||
| 	if (!info) { | ||||
| 		dev_err(&pdev->dev, "Failed to allocate memory\n"); | ||||
| 		ret = -ENOMEM; | ||||
| 		goto err; | ||||
| 		return -ENOMEM; | ||||
| 	} | ||||
| 
 | ||||
| 	info->micvdd = devm_regulator_get(arizona->dev, "MICVDD"); | ||||
| 	if (IS_ERR(info->micvdd)) { | ||||
| 		ret = PTR_ERR(info->micvdd); | ||||
| 		dev_err(arizona->dev, "Failed to get MICVDD: %d\n", ret); | ||||
| 		goto err; | ||||
| 		return ret; | ||||
| 	} | ||||
| 
 | ||||
| 	mutex_init(&info->lock); | ||||
|  | @ -1151,15 +1150,19 @@ static int arizona_extcon_probe(struct platform_device *pdev) | |||
| 		break; | ||||
| 	} | ||||
| 
 | ||||
| 	info->edev.name = "Headset Jack"; | ||||
| 	info->edev.dev.parent = arizona->dev; | ||||
| 	info->edev.supported_cable = arizona_cable; | ||||
| 	info->edev = devm_extcon_dev_allocate(&pdev->dev, arizona_cable); | ||||
| 	if (IS_ERR(info->edev)) { | ||||
| 		dev_err(&pdev->dev, "failed to allocate extcon device\n"); | ||||
| 		return -ENOMEM; | ||||
| 	} | ||||
| 	info->edev->name = "Headset Jack"; | ||||
| 	info->edev->dev.parent = arizona->dev; | ||||
| 
 | ||||
| 	ret = extcon_dev_register(&info->edev); | ||||
| 	ret = devm_extcon_dev_register(&pdev->dev, info->edev); | ||||
| 	if (ret < 0) { | ||||
| 		dev_err(arizona->dev, "extcon_dev_register() failed: %d\n", | ||||
| 			ret); | ||||
| 		goto err; | ||||
| 		return ret; | ||||
| 	} | ||||
| 
 | ||||
| 	info->input = devm_input_allocate_device(&pdev->dev); | ||||
|  | @ -1410,8 +1413,6 @@ err_rise: | |||
| err_input: | ||||
| err_register: | ||||
| 	pm_runtime_disable(&pdev->dev); | ||||
| 	extcon_dev_unregister(&info->edev); | ||||
| err: | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
|  | @ -1445,7 +1446,6 @@ static int arizona_extcon_remove(struct platform_device *pdev) | |||
| 	regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_ANALOGUE, | ||||
| 			   ARIZONA_JD1_ENA, 0); | ||||
| 	arizona_clk32k_disable(arizona); | ||||
| 	extcon_dev_unregister(&info->edev); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
|  |  | |||
|  | @ -565,6 +565,100 @@ static void dummy_sysfs_dev_release(struct device *dev) | |||
| { | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * extcon_dev_allocate() - Allocate the memory of extcon device. | ||||
|  * @supported_cable:	Array of supported cable names ending with NULL. | ||||
|  *			If supported_cable is NULL, cable name related APIs | ||||
|  *			are disabled. | ||||
|  * | ||||
|  * This function allocates the memory for extcon device without allocating | ||||
|  * memory in each extcon provider driver and initialize default setting for | ||||
|  * extcon device. | ||||
|  * | ||||
|  * Return the pointer of extcon device if success or ERR_PTR(err) if fail | ||||
|  */ | ||||
| struct extcon_dev *extcon_dev_allocate(const char **supported_cable) | ||||
| { | ||||
| 	struct extcon_dev *edev; | ||||
| 
 | ||||
| 	edev = kzalloc(sizeof(*edev), GFP_KERNEL); | ||||
| 	if (!edev) | ||||
| 		return ERR_PTR(-ENOMEM); | ||||
| 
 | ||||
| 	edev->max_supported = 0; | ||||
| 	edev->supported_cable = supported_cable; | ||||
| 
 | ||||
| 	return edev; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * extcon_dev_free() - Free the memory of extcon device. | ||||
|  * @edev:	the extcon device to free | ||||
|  */ | ||||
| void extcon_dev_free(struct extcon_dev *edev) | ||||
| { | ||||
| 	kfree(edev); | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(extcon_dev_free); | ||||
| 
 | ||||
| static int devm_extcon_dev_match(struct device *dev, void *res, void *data) | ||||
| { | ||||
| 	struct extcon_dev **r = res; | ||||
| 
 | ||||
| 	if (WARN_ON(!r || !*r)) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	return *r == data; | ||||
| } | ||||
| 
 | ||||
| static void devm_extcon_dev_release(struct device *dev, void *res) | ||||
| { | ||||
| 	extcon_dev_free(*(struct extcon_dev **)res); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * devm_extcon_dev_allocate - Allocate managed extcon device | ||||
|  * @dev:		device owning the extcon device being created | ||||
|  * @supported_cable:	Array of supported cable names ending with NULL. | ||||
|  *			If supported_cable is NULL, cable name related APIs | ||||
|  *			are disabled. | ||||
|  * | ||||
|  * This function manages automatically the memory of extcon device using device | ||||
|  * resource management and simplify the control of freeing the memory of extcon | ||||
|  * device. | ||||
|  * | ||||
|  * Returns the pointer memory of allocated extcon_dev if success | ||||
|  * or ERR_PTR(err) if fail | ||||
|  */ | ||||
| struct extcon_dev *devm_extcon_dev_allocate(struct device *dev, | ||||
| 					    const char **supported_cable) | ||||
| { | ||||
| 	struct extcon_dev **ptr, *edev; | ||||
| 
 | ||||
| 	ptr = devres_alloc(devm_extcon_dev_release, sizeof(*ptr), GFP_KERNEL); | ||||
| 	if (!ptr) | ||||
| 		return ERR_PTR(-ENOMEM); | ||||
| 
 | ||||
| 	edev = extcon_dev_allocate(supported_cable); | ||||
| 	if (IS_ERR(edev)) { | ||||
| 		devres_free(ptr); | ||||
| 		return edev; | ||||
| 	} | ||||
| 
 | ||||
| 	*ptr = edev; | ||||
| 	devres_add(dev, ptr); | ||||
| 
 | ||||
| 	return edev; | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(devm_extcon_dev_allocate); | ||||
| 
 | ||||
| void devm_extcon_dev_free(struct device *dev, struct extcon_dev *edev) | ||||
| { | ||||
| 	WARN_ON(devres_release(dev, devm_extcon_dev_release, | ||||
| 			       devm_extcon_dev_match, edev)); | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(devm_extcon_dev_free); | ||||
| 
 | ||||
| /**
 | ||||
|  * extcon_dev_register() - Register a new extcon device | ||||
|  * @edev	: the new extcon device (should be allocated before calling) | ||||
|  | @ -819,6 +913,63 @@ void extcon_dev_unregister(struct extcon_dev *edev) | |||
| } | ||||
| EXPORT_SYMBOL_GPL(extcon_dev_unregister); | ||||
| 
 | ||||
| static void devm_extcon_dev_unreg(struct device *dev, void *res) | ||||
| { | ||||
| 	extcon_dev_unregister(*(struct extcon_dev **)res); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * devm_extcon_dev_register() - Resource-managed extcon_dev_register() | ||||
|  * @dev:	device to allocate extcon device | ||||
|  * @edev:	the new extcon device to register | ||||
|  * | ||||
|  * Managed extcon_dev_register() function. If extcon device is attached with | ||||
|  * this function, that extcon device is automatically unregistered on driver | ||||
|  * detach. Internally this function calls extcon_dev_register() function. | ||||
|  * To get more information, refer that function. | ||||
|  * | ||||
|  * If extcon device is registered with this function and the device needs to be | ||||
|  * unregistered separately, devm_extcon_dev_unregister() should be used. | ||||
|  * | ||||
|  * Returns 0 if success or negaive error number if failure. | ||||
|  */ | ||||
| int devm_extcon_dev_register(struct device *dev, struct extcon_dev *edev) | ||||
| { | ||||
| 	struct extcon_dev **ptr; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	ptr = devres_alloc(devm_extcon_dev_unreg, sizeof(*ptr), GFP_KERNEL); | ||||
| 	if (!ptr) | ||||
| 		return -ENOMEM; | ||||
| 
 | ||||
| 	ret = extcon_dev_register(edev); | ||||
| 	if (ret) { | ||||
| 		devres_free(ptr); | ||||
| 		return ret; | ||||
| 	} | ||||
| 
 | ||||
| 	*ptr = edev; | ||||
| 	devres_add(dev, ptr); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(devm_extcon_dev_register); | ||||
| 
 | ||||
| /**
 | ||||
|  * devm_extcon_dev_unregister() - Resource-managed extcon_dev_unregister() | ||||
|  * @dev:	device the extcon belongs to | ||||
|  * @edev:	the extcon device to unregister | ||||
|  * | ||||
|  * Unregister extcon device that is registered with devm_extcon_dev_register() | ||||
|  * function. | ||||
|  */ | ||||
| void devm_extcon_dev_unregister(struct device *dev, struct extcon_dev *edev) | ||||
| { | ||||
| 	WARN_ON(devres_release(dev, devm_extcon_dev_unreg, | ||||
| 			       devm_extcon_dev_match, edev)); | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(devm_extcon_dev_unregister); | ||||
| 
 | ||||
| #ifdef CONFIG_OF | ||||
| /*
 | ||||
|  * extcon_get_edev_by_phandle - Get the extcon device from devicetree | ||||
|  |  | |||
|  | @ -32,7 +32,7 @@ | |||
| #include <linux/extcon/extcon-gpio.h> | ||||
| 
 | ||||
| struct gpio_extcon_data { | ||||
| 	struct extcon_dev edev; | ||||
| 	struct extcon_dev *edev; | ||||
| 	unsigned gpio; | ||||
| 	bool gpio_active_low; | ||||
| 	const char *state_on; | ||||
|  | @ -53,7 +53,7 @@ static void gpio_extcon_work(struct work_struct *work) | |||
| 	state = gpio_get_value(data->gpio); | ||||
| 	if (data->gpio_active_low) | ||||
| 		state = !state; | ||||
| 	extcon_set_state(&data->edev, state); | ||||
| 	extcon_set_state(data->edev, state); | ||||
| } | ||||
| 
 | ||||
| static irqreturn_t gpio_irq_handler(int irq, void *dev_id) | ||||
|  | @ -67,9 +67,10 @@ static irqreturn_t gpio_irq_handler(int irq, void *dev_id) | |||
| 
 | ||||
| static ssize_t extcon_gpio_print_state(struct extcon_dev *edev, char *buf) | ||||
| { | ||||
| 	struct gpio_extcon_data	*extcon_data = | ||||
| 		container_of(edev, struct gpio_extcon_data, edev); | ||||
| 	struct device *dev = edev->dev.parent; | ||||
| 	struct gpio_extcon_data *extcon_data = dev_get_drvdata(dev); | ||||
| 	const char *state; | ||||
| 
 | ||||
| 	if (extcon_get_state(edev)) | ||||
| 		state = extcon_data->state_on; | ||||
| 	else | ||||
|  | @ -98,15 +99,21 @@ static int gpio_extcon_probe(struct platform_device *pdev) | |||
| 	if (!extcon_data) | ||||
| 		return -ENOMEM; | ||||
| 
 | ||||
| 	extcon_data->edev.name = pdata->name; | ||||
| 	extcon_data->edev.dev.parent = &pdev->dev; | ||||
| 	extcon_data->edev = devm_extcon_dev_allocate(&pdev->dev, NULL); | ||||
| 	if (IS_ERR(extcon_data->edev)) { | ||||
| 		dev_err(&pdev->dev, "failed to allocate extcon device\n"); | ||||
| 		return -ENOMEM; | ||||
| 	} | ||||
| 	extcon_data->edev->name = pdata->name; | ||||
| 	extcon_data->edev->dev.parent = &pdev->dev; | ||||
| 
 | ||||
| 	extcon_data->gpio = pdata->gpio; | ||||
| 	extcon_data->gpio_active_low = pdata->gpio_active_low; | ||||
| 	extcon_data->state_on = pdata->state_on; | ||||
| 	extcon_data->state_off = pdata->state_off; | ||||
| 	extcon_data->check_on_resume = pdata->check_on_resume; | ||||
| 	if (pdata->state_on && pdata->state_off) | ||||
| 		extcon_data->edev.print_state = extcon_gpio_print_state; | ||||
| 		extcon_data->edev->print_state = extcon_gpio_print_state; | ||||
| 
 | ||||
| 	ret = devm_gpio_request_one(&pdev->dev, extcon_data->gpio, GPIOF_DIR_IN, | ||||
| 				    pdev->name); | ||||
|  | @ -121,34 +128,27 @@ static int gpio_extcon_probe(struct platform_device *pdev) | |||
| 				msecs_to_jiffies(pdata->debounce); | ||||
| 	} | ||||
| 
 | ||||
| 	ret = extcon_dev_register(&extcon_data->edev); | ||||
| 	ret = devm_extcon_dev_register(&pdev->dev, extcon_data->edev); | ||||
| 	if (ret < 0) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	INIT_DELAYED_WORK(&extcon_data->work, gpio_extcon_work); | ||||
| 
 | ||||
| 	extcon_data->irq = gpio_to_irq(extcon_data->gpio); | ||||
| 	if (extcon_data->irq < 0) { | ||||
| 		ret = extcon_data->irq; | ||||
| 		goto err; | ||||
| 	} | ||||
| 	if (extcon_data->irq < 0) | ||||
| 		return extcon_data->irq; | ||||
| 
 | ||||
| 	ret = request_any_context_irq(extcon_data->irq, gpio_irq_handler, | ||||
| 				      pdata->irq_flags, pdev->name, | ||||
| 				      extcon_data); | ||||
| 	if (ret < 0) | ||||
| 		goto err; | ||||
| 		return ret; | ||||
| 
 | ||||
| 	platform_set_drvdata(pdev, extcon_data); | ||||
| 	/* Perform initial detection */ | ||||
| 	gpio_extcon_work(&extcon_data->work.work); | ||||
| 
 | ||||
| 	return 0; | ||||
| 
 | ||||
| err: | ||||
| 	extcon_dev_unregister(&extcon_data->edev); | ||||
| 
 | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| static int gpio_extcon_remove(struct platform_device *pdev) | ||||
|  | @ -157,7 +157,6 @@ static int gpio_extcon_remove(struct platform_device *pdev) | |||
| 
 | ||||
| 	cancel_delayed_work_sync(&extcon_data->work); | ||||
| 	free_irq(extcon_data->irq, extcon_data); | ||||
| 	extcon_dev_unregister(&extcon_data->edev); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
|  |  | |||
|  | @ -1,8 +1,9 @@ | |||
| /*
 | ||||
|  * extcon-max14577.c - MAX14577 extcon driver to support MAX14577 MUIC | ||||
|  * extcon-max14577.c - MAX14577/77836 extcon driver to support MUIC | ||||
|  * | ||||
|  * Copyright (C) 2013 Samsung Electrnoics | ||||
|  * Copyright (C) 2013,2014 Samsung Electrnoics | ||||
|  * Chanwoo Choi <cw00.choi@samsung.com> | ||||
|  * Krzysztof Kozlowski <k.kozlowski@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 | ||||
|  | @ -24,7 +25,6 @@ | |||
| #include <linux/mfd/max14577-private.h> | ||||
| #include <linux/extcon.h> | ||||
| 
 | ||||
| #define	DEV_NAME			"max14577-muic" | ||||
| #define	DELAY_MS_DEFAULT		17000		/* unit: millisecond */ | ||||
| 
 | ||||
| enum max14577_muic_adc_debounce_time { | ||||
|  | @ -40,6 +40,42 @@ enum max14577_muic_status { | |||
| 	MAX14577_MUIC_STATUS_END, | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * struct max14577_muic_irq | ||||
|  * @irq: the index of irq list of MUIC device. | ||||
|  * @name: the name of irq. | ||||
|  * @virq: the virtual irq to use irq domain | ||||
|  */ | ||||
| struct max14577_muic_irq { | ||||
| 	unsigned int irq; | ||||
| 	const char *name; | ||||
| 	unsigned int virq; | ||||
| }; | ||||
| 
 | ||||
| static struct max14577_muic_irq max14577_muic_irqs[] = { | ||||
| 	{ MAX14577_IRQ_INT1_ADC,	"muic-ADC" }, | ||||
| 	{ MAX14577_IRQ_INT1_ADCLOW,	"muic-ADCLOW" }, | ||||
| 	{ MAX14577_IRQ_INT1_ADCERR,	"muic-ADCError" }, | ||||
| 	{ MAX14577_IRQ_INT2_CHGTYP,	"muic-CHGTYP" }, | ||||
| 	{ MAX14577_IRQ_INT2_CHGDETRUN,	"muic-CHGDETRUN" }, | ||||
| 	{ MAX14577_IRQ_INT2_DCDTMR,	"muic-DCDTMR" }, | ||||
| 	{ MAX14577_IRQ_INT2_DBCHG,	"muic-DBCHG" }, | ||||
| 	{ MAX14577_IRQ_INT2_VBVOLT,	"muic-VBVOLT" }, | ||||
| }; | ||||
| 
 | ||||
| static struct max14577_muic_irq max77836_muic_irqs[] = { | ||||
| 	{ MAX14577_IRQ_INT1_ADC,	"muic-ADC" }, | ||||
| 	{ MAX14577_IRQ_INT1_ADCLOW,	"muic-ADCLOW" }, | ||||
| 	{ MAX14577_IRQ_INT1_ADCERR,	"muic-ADCError" }, | ||||
| 	{ MAX77836_IRQ_INT1_ADC1K,	"muic-ADC1K" }, | ||||
| 	{ MAX14577_IRQ_INT2_CHGTYP,	"muic-CHGTYP" }, | ||||
| 	{ MAX14577_IRQ_INT2_CHGDETRUN,	"muic-CHGDETRUN" }, | ||||
| 	{ MAX14577_IRQ_INT2_DCDTMR,	"muic-DCDTMR" }, | ||||
| 	{ MAX14577_IRQ_INT2_DBCHG,	"muic-DBCHG" }, | ||||
| 	{ MAX14577_IRQ_INT2_VBVOLT,	"muic-VBVOLT" }, | ||||
| 	{ MAX77836_IRQ_INT2_VIDRM,	"muic-VIDRM" }, | ||||
| }; | ||||
| 
 | ||||
| struct max14577_muic_info { | ||||
| 	struct device *dev; | ||||
| 	struct max14577 *max14577; | ||||
|  | @ -48,6 +84,8 @@ struct max14577_muic_info { | |||
| 	int prev_chg_type; | ||||
| 	u8 status[MAX14577_MUIC_STATUS_END]; | ||||
| 
 | ||||
| 	struct max14577_muic_irq *muic_irqs; | ||||
| 	unsigned int muic_irqs_num; | ||||
| 	bool irq_adc; | ||||
| 	bool irq_chg; | ||||
| 	struct work_struct irq_work; | ||||
|  | @ -74,29 +112,6 @@ enum max14577_muic_cable_group { | |||
| 	MAX14577_CABLE_GROUP_CHG, | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * struct max14577_muic_irq | ||||
|  * @irq: the index of irq list of MUIC device. | ||||
|  * @name: the name of irq. | ||||
|  * @virq: the virtual irq to use irq domain | ||||
|  */ | ||||
| struct max14577_muic_irq { | ||||
| 	unsigned int irq; | ||||
| 	const char *name; | ||||
| 	unsigned int virq; | ||||
| }; | ||||
| 
 | ||||
| static struct max14577_muic_irq muic_irqs[] = { | ||||
| 	{ MAX14577_IRQ_INT1_ADC,	"muic-ADC" }, | ||||
| 	{ MAX14577_IRQ_INT1_ADCLOW,	"muic-ADCLOW" }, | ||||
| 	{ MAX14577_IRQ_INT1_ADCERR,	"muic-ADCError" }, | ||||
| 	{ MAX14577_IRQ_INT2_CHGTYP,	"muic-CHGTYP" }, | ||||
| 	{ MAX14577_IRQ_INT2_CHGDETRUN,	"muic-CHGDETRUN" }, | ||||
| 	{ MAX14577_IRQ_INT2_DCDTMR,	"muic-DCDTMR" }, | ||||
| 	{ MAX14577_IRQ_INT2_DBCHG,	"muic-DBCHG" }, | ||||
| 	{ MAX14577_IRQ_INT2_VBVOLT,	"muic-VBVOLT" }, | ||||
| }; | ||||
| 
 | ||||
| /* Define supported accessory type */ | ||||
| enum max14577_muic_acc_type { | ||||
| 	MAX14577_MUIC_ADC_GROUND = 0x0, | ||||
|  | @ -528,21 +543,12 @@ static void max14577_muic_irq_work(struct work_struct *work) | |||
| 	return; | ||||
| } | ||||
| 
 | ||||
| static irqreturn_t max14577_muic_irq_handler(int irq, void *data) | ||||
| /*
 | ||||
|  * Sets irq_adc or irq_chg in max14577_muic_info and returns 1. | ||||
|  * Returns 0 if irq_type does not match registered IRQ for this device type. | ||||
|  */ | ||||
| static int max14577_parse_irq(struct max14577_muic_info *info, int irq_type) | ||||
| { | ||||
| 	struct max14577_muic_info *info = data; | ||||
| 	int i, irq_type = -1; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * We may be called multiple times for different nested IRQ-s. | ||||
| 	 * Including changes in INT1_ADC and INT2_CGHTYP at once. | ||||
| 	 * However we only need to know whether it was ADC, charger | ||||
| 	 * or both interrupts so decode IRQ and turn on proper flags. | ||||
| 	 */ | ||||
| 	for (i = 0; i < ARRAY_SIZE(muic_irqs); i++) | ||||
| 		if (irq == muic_irqs[i].virq) | ||||
| 			irq_type = muic_irqs[i].irq; | ||||
| 
 | ||||
| 	switch (irq_type) { | ||||
| 	case MAX14577_IRQ_INT1_ADC: | ||||
| 	case MAX14577_IRQ_INT1_ADCLOW: | ||||
|  | @ -550,7 +556,7 @@ static irqreturn_t max14577_muic_irq_handler(int irq, void *data) | |||
| 		/* Handle all of accessory except for
 | ||||
| 		   type of charger accessory */ | ||||
| 		info->irq_adc = true; | ||||
| 		break; | ||||
| 		return 1; | ||||
| 	case MAX14577_IRQ_INT2_CHGTYP: | ||||
| 	case MAX14577_IRQ_INT2_CHGDETRUN: | ||||
| 	case MAX14577_IRQ_INT2_DCDTMR: | ||||
|  | @ -558,8 +564,62 @@ static irqreturn_t max14577_muic_irq_handler(int irq, void *data) | |||
| 	case MAX14577_IRQ_INT2_VBVOLT: | ||||
| 		/* Handle charger accessory */ | ||||
| 		info->irq_chg = true; | ||||
| 		break; | ||||
| 		return 1; | ||||
| 	default: | ||||
| 		return 0; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Sets irq_adc or irq_chg in max14577_muic_info and returns 1. | ||||
|  * Returns 0 if irq_type does not match registered IRQ for this device type. | ||||
|  */ | ||||
| static int max77836_parse_irq(struct max14577_muic_info *info, int irq_type) | ||||
| { | ||||
| 	/* First check common max14577 interrupts */ | ||||
| 	if (max14577_parse_irq(info, irq_type)) | ||||
| 		return 1; | ||||
| 
 | ||||
| 	switch (irq_type) { | ||||
| 	case MAX77836_IRQ_INT1_ADC1K: | ||||
| 		info->irq_adc = true; | ||||
| 		return 1; | ||||
| 	case MAX77836_IRQ_INT2_VIDRM: | ||||
| 		/* Handle charger accessory */ | ||||
| 		info->irq_chg = true; | ||||
| 		return 1; | ||||
| 	default: | ||||
| 		return 0; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static irqreturn_t max14577_muic_irq_handler(int irq, void *data) | ||||
| { | ||||
| 	struct max14577_muic_info *info = data; | ||||
| 	int i, irq_type = -1; | ||||
| 	bool irq_parsed; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * We may be called multiple times for different nested IRQ-s. | ||||
| 	 * Including changes in INT1_ADC and INT2_CGHTYP at once. | ||||
| 	 * However we only need to know whether it was ADC, charger | ||||
| 	 * or both interrupts so decode IRQ and turn on proper flags. | ||||
| 	 */ | ||||
| 	for (i = 0; i < info->muic_irqs_num; i++) | ||||
| 		if (irq == info->muic_irqs[i].virq) | ||||
| 			irq_type = info->muic_irqs[i].irq; | ||||
| 
 | ||||
| 	switch (info->max14577->dev_type) { | ||||
| 	case MAXIM_DEVICE_TYPE_MAX77836: | ||||
| 		irq_parsed = max77836_parse_irq(info, irq_type); | ||||
| 		break; | ||||
| 	case MAXIM_DEVICE_TYPE_MAX14577: | ||||
| 	default: | ||||
| 		irq_parsed = max14577_parse_irq(info, irq_type); | ||||
| 		break; | ||||
| 	} | ||||
| 
 | ||||
| 	if (!irq_parsed) { | ||||
| 		dev_err(info->dev, "muic interrupt: irq %d occurred, skipped\n", | ||||
| 				irq_type); | ||||
| 		return IRQ_HANDLED; | ||||
|  | @ -644,13 +704,24 @@ static int max14577_muic_probe(struct platform_device *pdev) | |||
| 
 | ||||
| 	INIT_WORK(&info->irq_work, max14577_muic_irq_work); | ||||
| 
 | ||||
| 	switch (max14577->dev_type) { | ||||
| 	case MAXIM_DEVICE_TYPE_MAX77836: | ||||
| 		info->muic_irqs = max77836_muic_irqs; | ||||
| 		info->muic_irqs_num = ARRAY_SIZE(max77836_muic_irqs); | ||||
| 		break; | ||||
| 	case MAXIM_DEVICE_TYPE_MAX14577: | ||||
| 	default: | ||||
| 		info->muic_irqs = max14577_muic_irqs; | ||||
| 		info->muic_irqs_num = ARRAY_SIZE(max14577_muic_irqs); | ||||
| 	} | ||||
| 
 | ||||
| 	/* Support irq domain for max14577 MUIC device */ | ||||
| 	for (i = 0; i < ARRAY_SIZE(muic_irqs); i++) { | ||||
| 		struct max14577_muic_irq *muic_irq = &muic_irqs[i]; | ||||
| 	for (i = 0; i < info->muic_irqs_num; i++) { | ||||
| 		struct max14577_muic_irq *muic_irq = &info->muic_irqs[i]; | ||||
| 		unsigned int virq = 0; | ||||
| 
 | ||||
| 		virq = regmap_irq_get_virq(max14577->irq_data, muic_irq->irq); | ||||
| 		if (!virq) | ||||
| 		if (virq <= 0) | ||||
| 			return -EINVAL; | ||||
| 		muic_irq->virq = virq; | ||||
| 
 | ||||
|  | @ -668,14 +739,16 @@ static int max14577_muic_probe(struct platform_device *pdev) | |||
| 	} | ||||
| 
 | ||||
| 	/* Initialize extcon device */ | ||||
| 	info->edev = devm_kzalloc(&pdev->dev, sizeof(*info->edev), GFP_KERNEL); | ||||
| 	if (!info->edev) { | ||||
| 	info->edev = devm_extcon_dev_allocate(&pdev->dev, | ||||
| 					      max14577_extcon_cable); | ||||
| 	if (IS_ERR(info->edev)) { | ||||
| 		dev_err(&pdev->dev, "failed to allocate memory for extcon\n"); | ||||
| 		return -ENOMEM; | ||||
| 	} | ||||
| 	info->edev->name = DEV_NAME; | ||||
| 	info->edev->supported_cable = max14577_extcon_cable; | ||||
| 	ret = extcon_dev_register(info->edev); | ||||
| 
 | ||||
| 	info->edev->name = dev_name(&pdev->dev); | ||||
| 
 | ||||
| 	ret = devm_extcon_dev_register(&pdev->dev, info->edev); | ||||
| 	if (ret) { | ||||
| 		dev_err(&pdev->dev, "failed to register extcon device\n"); | ||||
| 		return ret; | ||||
|  | @ -694,7 +767,7 @@ static int max14577_muic_probe(struct platform_device *pdev) | |||
| 			MAX14577_REG_DEVICEID, &id); | ||||
| 	if (ret < 0) { | ||||
| 		dev_err(&pdev->dev, "failed to read revision number\n"); | ||||
| 		goto err_extcon; | ||||
| 		return ret; | ||||
| 	} | ||||
| 	dev_info(info->dev, "device ID : 0x%x\n", id); | ||||
| 
 | ||||
|  | @ -710,19 +783,10 @@ static int max14577_muic_probe(struct platform_device *pdev) | |||
| 	 * driver should notify cable state to upper layer. | ||||
| 	 */ | ||||
| 	INIT_DELAYED_WORK(&info->wq_detcable, max14577_muic_detect_cable_wq); | ||||
| 	ret = queue_delayed_work(system_power_efficient_wq, &info->wq_detcable, | ||||
| 	queue_delayed_work(system_power_efficient_wq, &info->wq_detcable, | ||||
| 			delay_jiffies); | ||||
| 	if (ret < 0) { | ||||
| 		dev_err(&pdev->dev, | ||||
| 			"failed to schedule delayed work for cable detect\n"); | ||||
| 		goto err_extcon; | ||||
| 	} | ||||
| 
 | ||||
| 	return ret; | ||||
| 
 | ||||
| err_extcon: | ||||
| 	extcon_dev_unregister(info->edev); | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| static int max14577_muic_remove(struct platform_device *pdev) | ||||
|  | @ -730,23 +794,30 @@ static int max14577_muic_remove(struct platform_device *pdev) | |||
| 	struct max14577_muic_info *info = platform_get_drvdata(pdev); | ||||
| 
 | ||||
| 	cancel_work_sync(&info->irq_work); | ||||
| 	extcon_dev_unregister(info->edev); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static const struct platform_device_id max14577_muic_id[] = { | ||||
| 	{ "max14577-muic", MAXIM_DEVICE_TYPE_MAX14577, }, | ||||
| 	{ "max77836-muic", MAXIM_DEVICE_TYPE_MAX77836, }, | ||||
| 	{ } | ||||
| }; | ||||
| MODULE_DEVICE_TABLE(platform, max14577_muic_id); | ||||
| 
 | ||||
| static struct platform_driver max14577_muic_driver = { | ||||
| 	.driver		= { | ||||
| 		.name	= DEV_NAME, | ||||
| 		.name	= "max14577-muic", | ||||
| 		.owner	= THIS_MODULE, | ||||
| 	}, | ||||
| 	.probe		= max14577_muic_probe, | ||||
| 	.remove		= max14577_muic_remove, | ||||
| 	.id_table	= max14577_muic_id, | ||||
| }; | ||||
| 
 | ||||
| module_platform_driver(max14577_muic_driver); | ||||
| 
 | ||||
| MODULE_DESCRIPTION("MAXIM 14577 Extcon driver"); | ||||
| MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>"); | ||||
| MODULE_DESCRIPTION("Maxim 14577/77836 Extcon driver"); | ||||
| MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>, Krzysztof Kozlowski <k.kozlowski@samsung.com>"); | ||||
| MODULE_LICENSE("GPL"); | ||||
| MODULE_ALIAS("platform:extcon-max14577"); | ||||
|  |  | |||
|  | @ -1175,25 +1175,24 @@ static int max77693_muic_probe(struct platform_device *pdev) | |||
| 	} | ||||
| 
 | ||||
| 	/* Initialize extcon device */ | ||||
| 	info->edev = devm_kzalloc(&pdev->dev, sizeof(struct extcon_dev), | ||||
| 				  GFP_KERNEL); | ||||
| 	if (!info->edev) { | ||||
| 	info->edev = devm_extcon_dev_allocate(&pdev->dev, | ||||
| 					      max77693_extcon_cable); | ||||
| 	if (IS_ERR(info->edev)) { | ||||
| 		dev_err(&pdev->dev, "failed to allocate memory for extcon\n"); | ||||
| 		ret = -ENOMEM; | ||||
| 		goto err_irq; | ||||
| 	} | ||||
| 	info->edev->name = DEV_NAME; | ||||
| 	info->edev->dev.parent = &pdev->dev; | ||||
| 	info->edev->supported_cable = max77693_extcon_cable; | ||||
| 	ret = extcon_dev_register(info->edev); | ||||
| 
 | ||||
| 	ret = devm_extcon_dev_register(&pdev->dev, info->edev); | ||||
| 	if (ret) { | ||||
| 		dev_err(&pdev->dev, "failed to register extcon device\n"); | ||||
| 		goto err_irq; | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| 	/* Initialize MUIC register by using platform data or default data */ | ||||
| 	if (pdata->muic_data) { | ||||
| 	if (pdata && pdata->muic_data) { | ||||
| 		init_data = pdata->muic_data->init_data; | ||||
| 		num_init_data = pdata->muic_data->num_init_data; | ||||
| 	} else { | ||||
|  | @ -1226,7 +1225,7 @@ static int max77693_muic_probe(struct platform_device *pdev) | |||
| 				= init_data[i].data; | ||||
| 	} | ||||
| 
 | ||||
| 	if (pdata->muic_data) { | ||||
| 	if (pdata && pdata->muic_data) { | ||||
| 		struct max77693_muic_platform_data *muic_pdata | ||||
| 						   = pdata->muic_data; | ||||
| 
 | ||||
|  | @ -1267,7 +1266,7 @@ static int max77693_muic_probe(struct platform_device *pdev) | |||
| 			MAX77693_MUIC_REG_ID, &id); | ||||
| 	if (ret < 0) { | ||||
| 		dev_err(&pdev->dev, "failed to read revision number\n"); | ||||
| 		goto err_extcon; | ||||
| 		goto err_irq; | ||||
| 	} | ||||
| 	dev_info(info->dev, "device ID : 0x%x\n", id); | ||||
| 
 | ||||
|  | @ -1283,12 +1282,11 @@ static int max77693_muic_probe(struct platform_device *pdev) | |||
| 	 * driver should notify cable state to upper layer. | ||||
| 	 */ | ||||
| 	INIT_DELAYED_WORK(&info->wq_detcable, max77693_muic_detect_cable_wq); | ||||
| 	schedule_delayed_work(&info->wq_detcable, delay_jiffies); | ||||
| 	queue_delayed_work(system_power_efficient_wq, &info->wq_detcable, | ||||
| 			delay_jiffies); | ||||
| 
 | ||||
| 	return ret; | ||||
| 
 | ||||
| err_extcon: | ||||
| 	extcon_dev_unregister(info->edev); | ||||
| err_irq: | ||||
| 	while (--i >= 0) | ||||
| 		free_irq(muic_irqs[i].virq, info); | ||||
|  | @ -1304,7 +1302,6 @@ static int max77693_muic_remove(struct platform_device *pdev) | |||
| 		free_irq(muic_irqs[i].virq, info); | ||||
| 	cancel_work_sync(&info->irq_work); | ||||
| 	input_unregister_device(info->dock); | ||||
| 	extcon_dev_unregister(info->edev); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
|  |  | |||
|  | @ -699,23 +699,22 @@ static int max8997_muic_probe(struct platform_device *pdev) | |||
| 	} | ||||
| 
 | ||||
| 	/* External connector */ | ||||
| 	info->edev = devm_kzalloc(&pdev->dev, sizeof(struct extcon_dev), | ||||
| 				  GFP_KERNEL); | ||||
| 	if (!info->edev) { | ||||
| 	info->edev = devm_extcon_dev_allocate(&pdev->dev, max8997_extcon_cable); | ||||
| 	if (IS_ERR(info->edev)) { | ||||
| 		dev_err(&pdev->dev, "failed to allocate memory for extcon\n"); | ||||
| 		ret = -ENOMEM; | ||||
| 		goto err_irq; | ||||
| 	} | ||||
| 	info->edev->name = DEV_NAME; | ||||
| 	info->edev->dev.parent = &pdev->dev; | ||||
| 	info->edev->supported_cable = max8997_extcon_cable; | ||||
| 	ret = extcon_dev_register(info->edev); | ||||
| 
 | ||||
| 	ret = devm_extcon_dev_register(&pdev->dev, info->edev); | ||||
| 	if (ret) { | ||||
| 		dev_err(&pdev->dev, "failed to register extcon device\n"); | ||||
| 		goto err_irq; | ||||
| 	} | ||||
| 
 | ||||
| 	if (pdata->muic_pdata) { | ||||
| 	if (pdata && pdata->muic_pdata) { | ||||
| 		struct max8997_muic_platform_data *muic_pdata | ||||
| 			= pdata->muic_pdata; | ||||
| 
 | ||||
|  | @ -770,7 +769,8 @@ static int max8997_muic_probe(struct platform_device *pdev) | |||
| 	 * driver should notify cable state to upper layer. | ||||
| 	 */ | ||||
| 	INIT_DELAYED_WORK(&info->wq_detcable, max8997_muic_detect_cable_wq); | ||||
| 	schedule_delayed_work(&info->wq_detcable, delay_jiffies); | ||||
| 	queue_delayed_work(system_power_efficient_wq, &info->wq_detcable, | ||||
| 			delay_jiffies); | ||||
| 
 | ||||
| 	return 0; | ||||
| 
 | ||||
|  | @ -789,8 +789,6 @@ static int max8997_muic_remove(struct platform_device *pdev) | |||
| 		free_irq(muic_irqs[i].virq, info); | ||||
| 	cancel_work_sync(&info->irq_work); | ||||
| 
 | ||||
| 	extcon_dev_unregister(info->edev); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -23,6 +23,7 @@ | |||
| #include <linux/module.h> | ||||
| #include <linux/interrupt.h> | ||||
| #include <linux/platform_device.h> | ||||
| #include <linux/slab.h> | ||||
| #include <linux/err.h> | ||||
| #include <linux/mfd/palmas.h> | ||||
| #include <linux/of.h> | ||||
|  | @ -56,7 +57,7 @@ static irqreturn_t palmas_vbus_irq_handler(int irq, void *_palmas_usb) | |||
| 	if (vbus_line_state & PALMAS_INT3_LINE_STATE_VBUS) { | ||||
| 		if (palmas_usb->linkstat != PALMAS_USB_STATE_VBUS) { | ||||
| 			palmas_usb->linkstat = PALMAS_USB_STATE_VBUS; | ||||
| 			extcon_set_cable_state(&palmas_usb->edev, "USB", true); | ||||
| 			extcon_set_cable_state(palmas_usb->edev, "USB", true); | ||||
| 			dev_info(palmas_usb->dev, "USB cable is attached\n"); | ||||
| 		} else { | ||||
| 			dev_dbg(palmas_usb->dev, | ||||
|  | @ -65,7 +66,7 @@ static irqreturn_t palmas_vbus_irq_handler(int irq, void *_palmas_usb) | |||
| 	} else if (!(vbus_line_state & PALMAS_INT3_LINE_STATE_VBUS)) { | ||||
| 		if (palmas_usb->linkstat == PALMAS_USB_STATE_VBUS) { | ||||
| 			palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT; | ||||
| 			extcon_set_cable_state(&palmas_usb->edev, "USB", false); | ||||
| 			extcon_set_cable_state(palmas_usb->edev, "USB", false); | ||||
| 			dev_info(palmas_usb->dev, "USB cable is detached\n"); | ||||
| 		} else { | ||||
| 			dev_dbg(palmas_usb->dev, | ||||
|  | @ -92,7 +93,7 @@ static irqreturn_t palmas_id_irq_handler(int irq, void *_palmas_usb) | |||
| 			PALMAS_USB_ID_INT_LATCH_CLR, | ||||
| 			PALMAS_USB_ID_INT_EN_HI_CLR_ID_GND); | ||||
| 		palmas_usb->linkstat = PALMAS_USB_STATE_ID; | ||||
| 		extcon_set_cable_state(&palmas_usb->edev, "USB-HOST", true); | ||||
| 		extcon_set_cable_state(palmas_usb->edev, "USB-HOST", true); | ||||
| 		dev_info(palmas_usb->dev, "USB-HOST cable is attached\n"); | ||||
| 	} else if ((set & PALMAS_USB_ID_INT_SRC_ID_FLOAT) && | ||||
| 				(id_src & PALMAS_USB_ID_INT_SRC_ID_FLOAT)) { | ||||
|  | @ -100,17 +101,17 @@ static irqreturn_t palmas_id_irq_handler(int irq, void *_palmas_usb) | |||
| 			PALMAS_USB_ID_INT_LATCH_CLR, | ||||
| 			PALMAS_USB_ID_INT_EN_HI_CLR_ID_FLOAT); | ||||
| 		palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT; | ||||
| 		extcon_set_cable_state(&palmas_usb->edev, "USB-HOST", false); | ||||
| 		extcon_set_cable_state(palmas_usb->edev, "USB-HOST", false); | ||||
| 		dev_info(palmas_usb->dev, "USB-HOST cable is detached\n"); | ||||
| 	} else if ((palmas_usb->linkstat == PALMAS_USB_STATE_ID) && | ||||
| 				(!(set & PALMAS_USB_ID_INT_SRC_ID_GND))) { | ||||
| 		palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT; | ||||
| 		extcon_set_cable_state(&palmas_usb->edev, "USB-HOST", false); | ||||
| 		extcon_set_cable_state(palmas_usb->edev, "USB-HOST", false); | ||||
| 		dev_info(palmas_usb->dev, "USB-HOST cable is detached\n"); | ||||
| 	} else if ((palmas_usb->linkstat == PALMAS_USB_STATE_DISCONNECT) && | ||||
| 				(id_src & PALMAS_USB_ID_INT_SRC_ID_GND)) { | ||||
| 		palmas_usb->linkstat = PALMAS_USB_STATE_ID; | ||||
| 		extcon_set_cable_state(&palmas_usb->edev, "USB-HOST", true); | ||||
| 		extcon_set_cable_state(palmas_usb->edev, "USB-HOST", true); | ||||
| 		dev_info(palmas_usb->dev, " USB-HOST cable is attached\n"); | ||||
| 	} | ||||
| 
 | ||||
|  | @ -186,13 +187,20 @@ static int palmas_usb_probe(struct platform_device *pdev) | |||
| 
 | ||||
| 	platform_set_drvdata(pdev, palmas_usb); | ||||
| 
 | ||||
| 	palmas_usb->edev.supported_cable = palmas_extcon_cable; | ||||
| 	palmas_usb->edev.dev.parent = palmas_usb->dev; | ||||
| 	palmas_usb->edev.mutually_exclusive = mutually_exclusive; | ||||
| 	palmas_usb->edev = devm_extcon_dev_allocate(&pdev->dev, | ||||
| 						    palmas_extcon_cable); | ||||
| 	if (IS_ERR(palmas_usb->edev)) { | ||||
| 		dev_err(&pdev->dev, "failed to allocate extcon device\n"); | ||||
| 		return -ENOMEM; | ||||
| 	} | ||||
| 	palmas_usb->edev->name = kstrdup(node->name, GFP_KERNEL); | ||||
| 	palmas_usb->edev->dev.parent = palmas_usb->dev; | ||||
| 	palmas_usb->edev->mutually_exclusive = mutually_exclusive; | ||||
| 
 | ||||
| 	status = extcon_dev_register(&palmas_usb->edev); | ||||
| 	status = devm_extcon_dev_register(&pdev->dev, palmas_usb->edev); | ||||
| 	if (status) { | ||||
| 		dev_err(&pdev->dev, "failed to register extcon device\n"); | ||||
| 		kfree(palmas_usb->edev->name); | ||||
| 		return status; | ||||
| 	} | ||||
| 
 | ||||
|  | @ -206,7 +214,8 @@ static int palmas_usb_probe(struct platform_device *pdev) | |||
| 		if (status < 0) { | ||||
| 			dev_err(&pdev->dev, "can't get IRQ %d, err %d\n", | ||||
| 					palmas_usb->id_irq, status); | ||||
| 			goto fail_extcon; | ||||
| 			kfree(palmas_usb->edev->name); | ||||
| 			return status; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|  | @ -220,25 +229,21 @@ static int palmas_usb_probe(struct platform_device *pdev) | |||
| 		if (status < 0) { | ||||
| 			dev_err(&pdev->dev, "can't get IRQ %d, err %d\n", | ||||
| 					palmas_usb->vbus_irq, status); | ||||
| 			goto fail_extcon; | ||||
| 			kfree(palmas_usb->edev->name); | ||||
| 			return status; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	palmas_enable_irq(palmas_usb); | ||||
| 	device_set_wakeup_capable(&pdev->dev, true); | ||||
| 	return 0; | ||||
| 
 | ||||
| fail_extcon: | ||||
| 	extcon_dev_unregister(&palmas_usb->edev); | ||||
| 
 | ||||
| 	return status; | ||||
| } | ||||
| 
 | ||||
| static int palmas_usb_remove(struct platform_device *pdev) | ||||
| { | ||||
| 	struct palmas_usb *palmas_usb = platform_get_drvdata(pdev); | ||||
| 
 | ||||
| 	extcon_dev_unregister(&palmas_usb->edev); | ||||
| 	kfree(palmas_usb->edev->name); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
|  |  | |||
|  | @ -471,18 +471,26 @@ int vmbus_teardown_gpadl(struct vmbus_channel *channel, u32 gpadl_handle) | |||
| } | ||||
| EXPORT_SYMBOL_GPL(vmbus_teardown_gpadl); | ||||
| 
 | ||||
| static void reset_channel_cb(void *arg) | ||||
| { | ||||
| 	struct vmbus_channel *channel = arg; | ||||
| 
 | ||||
| 	channel->onchannel_callback = NULL; | ||||
| } | ||||
| 
 | ||||
| static void vmbus_close_internal(struct vmbus_channel *channel) | ||||
| { | ||||
| 	struct vmbus_channel_close_channel *msg; | ||||
| 	int ret; | ||||
| 	unsigned long flags; | ||||
| 
 | ||||
| 	channel->state = CHANNEL_OPEN_STATE; | ||||
| 	channel->sc_creation_callback = NULL; | ||||
| 	/* Stop callback and cancel the timer asap */ | ||||
| 	spin_lock_irqsave(&channel->inbound_lock, flags); | ||||
| 	channel->onchannel_callback = NULL; | ||||
| 	spin_unlock_irqrestore(&channel->inbound_lock, flags); | ||||
| 	if (channel->target_cpu != smp_processor_id()) | ||||
| 		smp_call_function_single(channel->target_cpu, reset_channel_cb, | ||||
| 					 channel, true); | ||||
| 	else | ||||
| 		reset_channel_cb(channel); | ||||
| 
 | ||||
| 	/* Send a closing message */ | ||||
| 
 | ||||
|  | @ -674,8 +682,7 @@ int vmbus_sendpacket_multipagebuffer(struct vmbus_channel *channel, | |||
| 	u32 pfncount = NUM_PAGES_SPANNED(multi_pagebuffer->offset, | ||||
| 					 multi_pagebuffer->len); | ||||
| 
 | ||||
| 
 | ||||
| 	if ((pfncount < 0) || (pfncount > MAX_MULTIPAGE_BUFFER_COUNT)) | ||||
| 	if (pfncount > MAX_MULTIPAGE_BUFFER_COUNT) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	/*
 | ||||
|  |  | |||
|  | @ -149,6 +149,7 @@ static struct vmbus_channel *alloc_channel(void) | |||
| 	spin_lock_init(&channel->sc_lock); | ||||
| 
 | ||||
| 	INIT_LIST_HEAD(&channel->sc_list); | ||||
| 	INIT_LIST_HEAD(&channel->percpu_list); | ||||
| 
 | ||||
| 	channel->controlwq = create_workqueue("hv_vmbus_ctl"); | ||||
| 	if (!channel->controlwq) { | ||||
|  | @ -188,7 +189,20 @@ static void free_channel(struct vmbus_channel *channel) | |||
| 	queue_work(vmbus_connection.work_queue, &channel->work); | ||||
| } | ||||
| 
 | ||||
| static void percpu_channel_enq(void *arg) | ||||
| { | ||||
| 	struct vmbus_channel *channel = arg; | ||||
| 	int cpu = smp_processor_id(); | ||||
| 
 | ||||
| 	list_add_tail(&channel->percpu_list, &hv_context.percpu_list[cpu]); | ||||
| } | ||||
| 
 | ||||
| static void percpu_channel_deq(void *arg) | ||||
| { | ||||
| 	struct vmbus_channel *channel = arg; | ||||
| 
 | ||||
| 	list_del(&channel->percpu_list); | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * vmbus_process_rescind_offer - | ||||
|  | @ -210,6 +224,12 @@ static void vmbus_process_rescind_offer(struct work_struct *work) | |||
| 	msg.header.msgtype = CHANNELMSG_RELID_RELEASED; | ||||
| 	vmbus_post_msg(&msg, sizeof(struct vmbus_channel_relid_released)); | ||||
| 
 | ||||
| 	if (channel->target_cpu != smp_processor_id()) | ||||
| 		smp_call_function_single(channel->target_cpu, | ||||
| 					 percpu_channel_deq, channel, true); | ||||
| 	else | ||||
| 		percpu_channel_deq(channel); | ||||
| 
 | ||||
| 	if (channel->primary_channel == NULL) { | ||||
| 		spin_lock_irqsave(&vmbus_connection.channel_lock, flags); | ||||
| 		list_del(&channel->listentry); | ||||
|  | @ -245,6 +265,7 @@ static void vmbus_process_offer(struct work_struct *work) | |||
| 							work); | ||||
| 	struct vmbus_channel *channel; | ||||
| 	bool fnew = true; | ||||
| 	bool enq = false; | ||||
| 	int ret; | ||||
| 	unsigned long flags; | ||||
| 
 | ||||
|  | @ -264,12 +285,22 @@ static void vmbus_process_offer(struct work_struct *work) | |||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if (fnew) | ||||
| 	if (fnew) { | ||||
| 		list_add_tail(&newchannel->listentry, | ||||
| 			      &vmbus_connection.chn_list); | ||||
| 		enq = true; | ||||
| 	} | ||||
| 
 | ||||
| 	spin_unlock_irqrestore(&vmbus_connection.channel_lock, flags); | ||||
| 
 | ||||
| 	if (enq) { | ||||
| 		if (newchannel->target_cpu != smp_processor_id()) | ||||
| 			smp_call_function_single(newchannel->target_cpu, | ||||
| 						 percpu_channel_enq, | ||||
| 						 newchannel, true); | ||||
| 		else | ||||
| 			percpu_channel_enq(newchannel); | ||||
| 	} | ||||
| 	if (!fnew) { | ||||
| 		/*
 | ||||
| 		 * Check to see if this is a sub-channel. | ||||
|  | @ -282,6 +313,14 @@ static void vmbus_process_offer(struct work_struct *work) | |||
| 			spin_lock_irqsave(&channel->sc_lock, flags); | ||||
| 			list_add_tail(&newchannel->sc_list, &channel->sc_list); | ||||
| 			spin_unlock_irqrestore(&channel->sc_lock, flags); | ||||
| 
 | ||||
| 			if (newchannel->target_cpu != smp_processor_id()) | ||||
| 				smp_call_function_single(newchannel->target_cpu, | ||||
| 							 percpu_channel_enq, | ||||
| 							 newchannel, true); | ||||
| 			else | ||||
| 				percpu_channel_enq(newchannel); | ||||
| 
 | ||||
| 			newchannel->state = CHANNEL_OPEN_STATE; | ||||
| 			if (channel->sc_creation_callback != NULL) | ||||
| 				channel->sc_creation_callback(newchannel); | ||||
|  | @ -365,7 +404,7 @@ static u32  next_vp; | |||
|  * performance critical channels (IDE, SCSI and Network) will be uniformly | ||||
|  * distributed across all available CPUs. | ||||
|  */ | ||||
| static u32 get_vp_index(uuid_le *type_guid) | ||||
| static void init_vp_index(struct vmbus_channel *channel, uuid_le *type_guid) | ||||
| { | ||||
| 	u32 cur_cpu; | ||||
| 	int i; | ||||
|  | @ -387,10 +426,13 @@ static u32 get_vp_index(uuid_le *type_guid) | |||
| 		 * Also if the channel is not a performance critical | ||||
| 		 * channel, bind it to cpu 0. | ||||
| 		 */ | ||||
| 		return 0; | ||||
| 		channel->target_cpu = 0; | ||||
| 		channel->target_vp = 0; | ||||
| 		return; | ||||
| 	} | ||||
| 	cur_cpu = (++next_vp % max_cpus); | ||||
| 	return hv_context.vp_index[cur_cpu]; | ||||
| 	channel->target_cpu = cur_cpu; | ||||
| 	channel->target_vp = hv_context.vp_index[cur_cpu]; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  | @ -438,7 +480,7 @@ static void vmbus_onoffer(struct vmbus_channel_message_header *hdr) | |||
| 				offer->connection_id; | ||||
| 	} | ||||
| 
 | ||||
| 	newchannel->target_vp = get_vp_index(&offer->offer.if_type); | ||||
| 	init_vp_index(newchannel, &offer->offer.if_type); | ||||
| 
 | ||||
| 	memcpy(&newchannel->offermsg, offer, | ||||
| 	       sizeof(struct vmbus_channel_offer_channel)); | ||||
|  |  | |||
|  | @ -224,8 +224,8 @@ cleanup: | |||
| 		vmbus_connection.int_page = NULL; | ||||
| 	} | ||||
| 
 | ||||
| 	free_pages((unsigned long)vmbus_connection.monitor_pages[0], 1); | ||||
| 	free_pages((unsigned long)vmbus_connection.monitor_pages[1], 1); | ||||
| 	free_pages((unsigned long)vmbus_connection.monitor_pages[0], 0); | ||||
| 	free_pages((unsigned long)vmbus_connection.monitor_pages[1], 0); | ||||
| 	vmbus_connection.monitor_pages[0] = NULL; | ||||
| 	vmbus_connection.monitor_pages[1] = NULL; | ||||
| 
 | ||||
|  | @ -234,6 +234,28 @@ cleanup: | |||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Map the given relid to the corresponding channel based on the | ||||
|  * per-cpu list of channels that have been affinitized to this CPU. | ||||
|  * This will be used in the channel callback path as we can do this | ||||
|  * mapping in a lock-free fashion. | ||||
|  */ | ||||
| static struct vmbus_channel *pcpu_relid2channel(u32 relid) | ||||
| { | ||||
| 	struct vmbus_channel *channel; | ||||
| 	struct vmbus_channel *found_channel  = NULL; | ||||
| 	int cpu = smp_processor_id(); | ||||
| 	struct list_head *pcpu_head = &hv_context.percpu_list[cpu]; | ||||
| 
 | ||||
| 	list_for_each_entry(channel, pcpu_head, percpu_list) { | ||||
| 		if (channel->offermsg.child_relid == relid) { | ||||
| 			found_channel = channel; | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return found_channel; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * relid2channel - Get the channel object given its | ||||
|  | @ -277,7 +299,6 @@ struct vmbus_channel *relid2channel(u32 relid) | |||
| static void process_chn_event(u32 relid) | ||||
| { | ||||
| 	struct vmbus_channel *channel; | ||||
| 	unsigned long flags; | ||||
| 	void *arg; | ||||
| 	bool read_state; | ||||
| 	u32 bytes_to_read; | ||||
|  | @ -286,7 +307,7 @@ static void process_chn_event(u32 relid) | |||
| 	 * Find the channel based on this relid and invokes the | ||||
| 	 * channel callback to process the event | ||||
| 	 */ | ||||
| 	channel = relid2channel(relid); | ||||
| 	channel = pcpu_relid2channel(relid); | ||||
| 
 | ||||
| 	if (!channel) { | ||||
| 		pr_err("channel not found for relid - %u\n", relid); | ||||
|  | @ -296,13 +317,12 @@ static void process_chn_event(u32 relid) | |||
| 	/*
 | ||||
| 	 * A channel once created is persistent even when there | ||||
| 	 * is no driver handling the device. An unloading driver | ||||
| 	 * sets the onchannel_callback to NULL under the | ||||
| 	 * protection of the channel inbound_lock. Thus, checking | ||||
| 	 * and invoking the driver specific callback takes care of | ||||
| 	 * orderly unloading of the driver. | ||||
| 	 * sets the onchannel_callback to NULL on the same CPU | ||||
| 	 * as where this interrupt is handled (in an interrupt context). | ||||
| 	 * Thus, checking and invoking the driver specific callback takes | ||||
| 	 * care of orderly unloading of the driver. | ||||
| 	 */ | ||||
| 
 | ||||
| 	spin_lock_irqsave(&channel->inbound_lock, flags); | ||||
| 	if (channel->onchannel_callback != NULL) { | ||||
| 		arg = channel->channel_callback_context; | ||||
| 		read_state = channel->batched_reading; | ||||
|  | @ -327,7 +347,6 @@ static void process_chn_event(u32 relid) | |||
| 		pr_err("no channel callback for relid - %u\n", relid); | ||||
| 	} | ||||
| 
 | ||||
| 	spin_unlock_irqrestore(&channel->inbound_lock, flags); | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  |  | |||
|  | @ -383,6 +383,8 @@ void hv_synic_init(void *arg) | |||
| 	 */ | ||||
| 	rdmsrl(HV_X64_MSR_VP_INDEX, vp_index); | ||||
| 	hv_context.vp_index[cpu] = (u32)vp_index; | ||||
| 
 | ||||
| 	INIT_LIST_HEAD(&hv_context.percpu_list[cpu]); | ||||
| 	return; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -19,6 +19,7 @@ | |||
| #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | ||||
| 
 | ||||
| #include <linux/kernel.h> | ||||
| #include <linux/jiffies.h> | ||||
| #include <linux/mman.h> | ||||
| #include <linux/delay.h> | ||||
| #include <linux/init.h> | ||||
|  | @ -459,6 +460,11 @@ static bool do_hot_add; | |||
|  */ | ||||
| static uint pressure_report_delay = 45; | ||||
| 
 | ||||
| /*
 | ||||
|  * The last time we posted a pressure report to host. | ||||
|  */ | ||||
| static unsigned long last_post_time; | ||||
| 
 | ||||
| module_param(hot_add, bool, (S_IRUGO | S_IWUSR)); | ||||
| MODULE_PARM_DESC(hot_add, "If set attempt memory hot_add"); | ||||
| 
 | ||||
|  | @ -542,6 +548,7 @@ struct hv_dynmem_device { | |||
| 
 | ||||
| static struct hv_dynmem_device dm_device; | ||||
| 
 | ||||
| static void post_status(struct hv_dynmem_device *dm); | ||||
| #ifdef CONFIG_MEMORY_HOTPLUG | ||||
| 
 | ||||
| static void hv_bring_pgs_online(unsigned long start_pfn, unsigned long size) | ||||
|  | @ -612,7 +619,7 @@ static void hv_mem_hot_add(unsigned long start, unsigned long size, | |||
| 		 * have not been "onlined" within the allowed time. | ||||
| 		 */ | ||||
| 		wait_for_completion_timeout(&dm_device.ol_waitevent, 5*HZ); | ||||
| 
 | ||||
| 		post_status(&dm_device); | ||||
| 	} | ||||
| 
 | ||||
| 	return; | ||||
|  | @ -951,11 +958,17 @@ static void post_status(struct hv_dynmem_device *dm) | |||
| { | ||||
| 	struct dm_status status; | ||||
| 	struct sysinfo val; | ||||
| 	unsigned long now = jiffies; | ||||
| 	unsigned long last_post = last_post_time; | ||||
| 
 | ||||
| 	if (pressure_report_delay > 0) { | ||||
| 		--pressure_report_delay; | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	if (!time_after(now, (last_post_time + HZ))) | ||||
| 		return; | ||||
| 
 | ||||
| 	si_meminfo(&val); | ||||
| 	memset(&status, 0, sizeof(struct dm_status)); | ||||
| 	status.hdr.type = DM_STATUS_REPORT; | ||||
|  | @ -983,6 +996,14 @@ static void post_status(struct hv_dynmem_device *dm) | |||
| 	if (status.hdr.trans_id != atomic_read(&trans_id)) | ||||
| 		return; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * If the last post time that we sampled has changed, | ||||
| 	 * we have raced, don't post the status. | ||||
| 	 */ | ||||
| 	if (last_post != last_post_time) | ||||
| 		return; | ||||
| 
 | ||||
| 	last_post_time = jiffies; | ||||
| 	vmbus_sendpacket(dm->dev->channel, &status, | ||||
| 				sizeof(struct dm_status), | ||||
| 				(unsigned long)NULL, | ||||
|  | @ -1117,7 +1138,7 @@ static void balloon_up(struct work_struct *dummy) | |||
| 
 | ||||
| 			if (ret == -EAGAIN) | ||||
| 				msleep(20); | ||||
| 
 | ||||
| 			post_status(&dm_device); | ||||
| 		} while (ret == -EAGAIN); | ||||
| 
 | ||||
| 		if (ret) { | ||||
|  | @ -1144,8 +1165,10 @@ static void balloon_down(struct hv_dynmem_device *dm, | |||
| 	struct dm_unballoon_response resp; | ||||
| 	int i; | ||||
| 
 | ||||
| 	for (i = 0; i < range_count; i++) | ||||
| 	for (i = 0; i < range_count; i++) { | ||||
| 		free_balloon_pages(dm, &range_array[i]); | ||||
| 		post_status(&dm_device); | ||||
| 	} | ||||
| 
 | ||||
| 	if (req->more_pages == 1) | ||||
| 		return; | ||||
|  |  | |||
|  | @ -510,6 +510,11 @@ struct hv_context { | |||
| 	 * basis. | ||||
| 	 */ | ||||
| 	struct tasklet_struct *event_dpc[NR_CPUS]; | ||||
| 	/*
 | ||||
| 	 * To optimize the mapping of relid to channel, maintain | ||||
| 	 * per-cpu list of the channels based on their CPU affinity. | ||||
| 	 */ | ||||
| 	struct list_head percpu_list[NR_CPUS]; | ||||
| }; | ||||
| 
 | ||||
| extern struct hv_context hv_context; | ||||
|  |  | |||
|  | @ -183,14 +183,14 @@ EXPORT_SYMBOL_GPL(mcb_device_register); | |||
|  * | ||||
|  * Allocate a new @mcb_bus. | ||||
|  */ | ||||
| struct mcb_bus *mcb_alloc_bus(void) | ||||
| struct mcb_bus *mcb_alloc_bus(struct device *carrier) | ||||
| { | ||||
| 	struct mcb_bus *bus; | ||||
| 	int bus_nr; | ||||
| 
 | ||||
| 	bus = kzalloc(sizeof(struct mcb_bus), GFP_KERNEL); | ||||
| 	if (!bus) | ||||
| 		return NULL; | ||||
| 		return ERR_PTR(-ENOMEM); | ||||
| 
 | ||||
| 	bus_nr = ida_simple_get(&mcb_ida, 0, 0, GFP_KERNEL); | ||||
| 	if (bus_nr < 0) { | ||||
|  | @ -200,7 +200,7 @@ struct mcb_bus *mcb_alloc_bus(void) | |||
| 
 | ||||
| 	INIT_LIST_HEAD(&bus->children); | ||||
| 	bus->bus_nr = bus_nr; | ||||
| 
 | ||||
| 	bus->carrier = carrier; | ||||
| 	return bus; | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(mcb_alloc_bus); | ||||
|  | @ -378,6 +378,13 @@ void mcb_release_mem(struct resource *mem) | |||
| } | ||||
| EXPORT_SYMBOL_GPL(mcb_release_mem); | ||||
| 
 | ||||
| static int __mcb_get_irq(struct mcb_device *dev) | ||||
| { | ||||
| 	struct resource *irq = &dev->irq; | ||||
| 
 | ||||
| 	return irq->start; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * mcb_get_irq() - Get device's IRQ number | ||||
|  * @dev: The @mcb_device the IRQ is for | ||||
|  | @ -386,9 +393,12 @@ EXPORT_SYMBOL_GPL(mcb_release_mem); | |||
|  */ | ||||
| int mcb_get_irq(struct mcb_device *dev) | ||||
| { | ||||
| 	struct resource *irq = &dev->irq; | ||||
| 	struct mcb_bus *bus = dev->bus; | ||||
| 
 | ||||
| 	return irq->start; | ||||
| 	if (bus->get_irq) | ||||
| 		return bus->get_irq(dev); | ||||
| 
 | ||||
| 	return __mcb_get_irq(dev); | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(mcb_get_irq); | ||||
| 
 | ||||
|  |  | |||
|  | @ -20,6 +20,15 @@ struct priv { | |||
| 	void __iomem *base; | ||||
| }; | ||||
| 
 | ||||
| static int mcb_pci_get_irq(struct mcb_device *mdev) | ||||
| { | ||||
| 	struct mcb_bus *mbus = mdev->bus; | ||||
| 	struct device *dev = mbus->carrier; | ||||
| 	struct pci_dev *pdev = to_pci_dev(dev); | ||||
| 
 | ||||
| 	return pdev->irq; | ||||
| } | ||||
| 
 | ||||
| static int mcb_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) | ||||
| { | ||||
| 	struct priv *priv; | ||||
|  | @ -67,7 +76,13 @@ static int mcb_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) | |||
| 
 | ||||
| 	pci_set_drvdata(pdev, priv); | ||||
| 
 | ||||
| 	priv->bus = mcb_alloc_bus(); | ||||
| 	priv->bus = mcb_alloc_bus(&pdev->dev); | ||||
| 	if (IS_ERR(priv->bus)) { | ||||
| 		ret = PTR_ERR(priv->bus); | ||||
| 		goto err_drvdata; | ||||
| 	} | ||||
| 
 | ||||
| 	priv->bus->get_irq = mcb_pci_get_irq; | ||||
| 
 | ||||
| 	ret = chameleon_parse_cells(priv->bus, mapbase, priv->base); | ||||
| 	if (ret < 0) | ||||
|  |  | |||
|  | @ -331,15 +331,15 @@ config MFD_88PM860X | |||
| 	  battery-charger under the corresponding menus. | ||||
| 
 | ||||
| config MFD_MAX14577 | ||||
| 	bool "Maxim Semiconductor MAX14577 MUIC + Charger Support" | ||||
| 	bool "Maxim Semiconductor MAX14577/77836 MUIC + Charger Support" | ||||
| 	depends on I2C=y | ||||
| 	select MFD_CORE | ||||
| 	select REGMAP_I2C | ||||
| 	select REGMAP_IRQ | ||||
| 	select IRQ_DOMAIN | ||||
| 	help | ||||
| 	  Say yes here to add support for Maxim Semiconductor MAX14577. | ||||
| 	  This is a Micro-USB IC with Charger controls on chip. | ||||
| 	  Say yes here to add support for Maxim Semiconductor MAX14577 and | ||||
| 	  MAX77836 Micro-USB ICs with battery charger. | ||||
| 	  This driver provides common support for accessing the device; | ||||
| 	  additional drivers must be enabled in order to use the functionality | ||||
| 	  of the device. | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| /*
 | ||||
|  * max14577.c - mfd core driver for the Maxim 14577 | ||||
|  * max14577.c - mfd core driver for the Maxim 14577/77836 | ||||
|  * | ||||
|  * Copyright (C) 2013 Samsung Electrnoics | ||||
|  * Copyright (C) 2014 Samsung Electrnoics | ||||
|  * Chanwoo Choi <cw00.choi@samsung.com> | ||||
|  * Krzysztof Kozlowski <k.kozlowski@samsung.com> | ||||
|  * | ||||
|  | @ -21,6 +21,7 @@ | |||
| #include <linux/err.h> | ||||
| #include <linux/module.h> | ||||
| #include <linux/interrupt.h> | ||||
| #include <linux/of_device.h> | ||||
| #include <linux/mfd/core.h> | ||||
| #include <linux/mfd/max14577.h> | ||||
| #include <linux/mfd/max14577-private.h> | ||||
|  | @ -37,7 +38,38 @@ static struct mfd_cell max14577_devs[] = { | |||
| 	{ .name = "max14577-charger", }, | ||||
| }; | ||||
| 
 | ||||
| static bool max14577_volatile_reg(struct device *dev, unsigned int reg) | ||||
| static struct mfd_cell max77836_devs[] = { | ||||
| 	{ | ||||
| 		.name = "max77836-muic", | ||||
| 		.of_compatible = "maxim,max77836-muic", | ||||
| 	}, | ||||
| 	{ | ||||
| 		.name = "max77836-regulator", | ||||
| 		.of_compatible = "maxim,max77836-regulator", | ||||
| 	}, | ||||
| 	{ | ||||
| 		.name = "max77836-charger", | ||||
| 		.of_compatible = "maxim,max77836-charger", | ||||
| 	}, | ||||
| 	{ | ||||
| 		.name = "max77836-battery", | ||||
| 		.of_compatible = "maxim,max77836-battery", | ||||
| 	}, | ||||
| }; | ||||
| 
 | ||||
| static struct of_device_id max14577_dt_match[] = { | ||||
| 	{ | ||||
| 		.compatible = "maxim,max14577", | ||||
| 		.data = (void *)MAXIM_DEVICE_TYPE_MAX14577, | ||||
| 	}, | ||||
| 	{ | ||||
| 		.compatible = "maxim,max77836", | ||||
| 		.data = (void *)MAXIM_DEVICE_TYPE_MAX77836, | ||||
| 	}, | ||||
| 	{}, | ||||
| }; | ||||
| 
 | ||||
| static bool max14577_muic_volatile_reg(struct device *dev, unsigned int reg) | ||||
| { | ||||
| 	switch (reg) { | ||||
| 	case MAX14577_REG_INT1 ... MAX14577_REG_STATUS3: | ||||
|  | @ -48,49 +80,221 @@ static bool max14577_volatile_reg(struct device *dev, unsigned int reg) | |||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| static const struct regmap_config max14577_regmap_config = { | ||||
| static bool max77836_muic_volatile_reg(struct device *dev, unsigned int reg) | ||||
| { | ||||
| 	/* Any max14577 volatile registers are also max77836 volatile. */ | ||||
| 	if (max14577_muic_volatile_reg(dev, reg)) | ||||
| 		return true; | ||||
| 
 | ||||
| 	switch (reg) { | ||||
| 	case MAX77836_FG_REG_VCELL_MSB ... MAX77836_FG_REG_SOC_LSB: | ||||
| 	case MAX77836_FG_REG_CRATE_MSB ... MAX77836_FG_REG_CRATE_LSB: | ||||
| 	case MAX77836_FG_REG_STATUS_H ... MAX77836_FG_REG_STATUS_L: | ||||
| 	case MAX77836_PMIC_REG_INTSRC: | ||||
| 	case MAX77836_PMIC_REG_TOPSYS_INT: | ||||
| 	case MAX77836_PMIC_REG_TOPSYS_STAT: | ||||
| 		return true; | ||||
| 	default: | ||||
| 		break; | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| static const struct regmap_config max14577_muic_regmap_config = { | ||||
| 	.reg_bits	= 8, | ||||
| 	.val_bits	= 8, | ||||
| 	.volatile_reg	= max14577_volatile_reg, | ||||
| 	.volatile_reg	= max14577_muic_volatile_reg, | ||||
| 	.max_register	= MAX14577_REG_END, | ||||
| }; | ||||
| 
 | ||||
| static const struct regmap_config max77836_pmic_regmap_config = { | ||||
| 	.reg_bits	= 8, | ||||
| 	.val_bits	= 8, | ||||
| 	.volatile_reg	= max77836_muic_volatile_reg, | ||||
| 	.max_register	= MAX77836_PMIC_REG_END, | ||||
| }; | ||||
| 
 | ||||
| static const struct regmap_irq max14577_irqs[] = { | ||||
| 	/* INT1 interrupts */ | ||||
| 	{ .reg_offset = 0, .mask = INT1_ADC_MASK, }, | ||||
| 	{ .reg_offset = 0, .mask = INT1_ADCLOW_MASK, }, | ||||
| 	{ .reg_offset = 0, .mask = INT1_ADCERR_MASK, }, | ||||
| 	{ .reg_offset = 0, .mask = MAX14577_INT1_ADC_MASK, }, | ||||
| 	{ .reg_offset = 0, .mask = MAX14577_INT1_ADCLOW_MASK, }, | ||||
| 	{ .reg_offset = 0, .mask = MAX14577_INT1_ADCERR_MASK, }, | ||||
| 	/* INT2 interrupts */ | ||||
| 	{ .reg_offset = 1, .mask = INT2_CHGTYP_MASK, }, | ||||
| 	{ .reg_offset = 1, .mask = INT2_CHGDETRUN_MASK, }, | ||||
| 	{ .reg_offset = 1, .mask = INT2_DCDTMR_MASK, }, | ||||
| 	{ .reg_offset = 1, .mask = INT2_DBCHG_MASK, }, | ||||
| 	{ .reg_offset = 1, .mask = INT2_VBVOLT_MASK, }, | ||||
| 	{ .reg_offset = 1, .mask = MAX14577_INT2_CHGTYP_MASK, }, | ||||
| 	{ .reg_offset = 1, .mask = MAX14577_INT2_CHGDETRUN_MASK, }, | ||||
| 	{ .reg_offset = 1, .mask = MAX14577_INT2_DCDTMR_MASK, }, | ||||
| 	{ .reg_offset = 1, .mask = MAX14577_INT2_DBCHG_MASK, }, | ||||
| 	{ .reg_offset = 1, .mask = MAX14577_INT2_VBVOLT_MASK, }, | ||||
| 	/* INT3 interrupts */ | ||||
| 	{ .reg_offset = 2, .mask = INT3_EOC_MASK, }, | ||||
| 	{ .reg_offset = 2, .mask = INT3_CGMBC_MASK, }, | ||||
| 	{ .reg_offset = 2, .mask = INT3_OVP_MASK, }, | ||||
| 	{ .reg_offset = 2, .mask = INT3_MBCCHGERR_MASK, }, | ||||
| 	{ .reg_offset = 2, .mask = MAX14577_INT3_EOC_MASK, }, | ||||
| 	{ .reg_offset = 2, .mask = MAX14577_INT3_CGMBC_MASK, }, | ||||
| 	{ .reg_offset = 2, .mask = MAX14577_INT3_OVP_MASK, }, | ||||
| 	{ .reg_offset = 2, .mask = MAX14577_INT3_MBCCHGERR_MASK, }, | ||||
| }; | ||||
| 
 | ||||
| static const struct regmap_irq_chip max14577_irq_chip = { | ||||
| 	.name			= "max14577", | ||||
| 	.status_base		= MAX14577_REG_INT1, | ||||
| 	.mask_base		= MAX14577_REG_INTMASK1, | ||||
| 	.mask_invert		= 1, | ||||
| 	.mask_invert		= true, | ||||
| 	.num_regs		= 3, | ||||
| 	.irqs			= max14577_irqs, | ||||
| 	.num_irqs		= ARRAY_SIZE(max14577_irqs), | ||||
| }; | ||||
| 
 | ||||
| static const struct regmap_irq max77836_muic_irqs[] = { | ||||
| 	/* INT1 interrupts */ | ||||
| 	{ .reg_offset = 0, .mask = MAX14577_INT1_ADC_MASK, }, | ||||
| 	{ .reg_offset = 0, .mask = MAX14577_INT1_ADCLOW_MASK, }, | ||||
| 	{ .reg_offset = 0, .mask = MAX14577_INT1_ADCERR_MASK, }, | ||||
| 	{ .reg_offset = 0, .mask = MAX77836_INT1_ADC1K_MASK, }, | ||||
| 	/* INT2 interrupts */ | ||||
| 	{ .reg_offset = 1, .mask = MAX14577_INT2_CHGTYP_MASK, }, | ||||
| 	{ .reg_offset = 1, .mask = MAX14577_INT2_CHGDETRUN_MASK, }, | ||||
| 	{ .reg_offset = 1, .mask = MAX14577_INT2_DCDTMR_MASK, }, | ||||
| 	{ .reg_offset = 1, .mask = MAX14577_INT2_DBCHG_MASK, }, | ||||
| 	{ .reg_offset = 1, .mask = MAX14577_INT2_VBVOLT_MASK, }, | ||||
| 	{ .reg_offset = 1, .mask = MAX77836_INT2_VIDRM_MASK, }, | ||||
| 	/* INT3 interrupts */ | ||||
| 	{ .reg_offset = 2, .mask = MAX14577_INT3_EOC_MASK, }, | ||||
| 	{ .reg_offset = 2, .mask = MAX14577_INT3_CGMBC_MASK, }, | ||||
| 	{ .reg_offset = 2, .mask = MAX14577_INT3_OVP_MASK, }, | ||||
| 	{ .reg_offset = 2, .mask = MAX14577_INT3_MBCCHGERR_MASK, }, | ||||
| }; | ||||
| 
 | ||||
| static const struct regmap_irq_chip max77836_muic_irq_chip = { | ||||
| 	.name			= "max77836-muic", | ||||
| 	.status_base		= MAX14577_REG_INT1, | ||||
| 	.mask_base		= MAX14577_REG_INTMASK1, | ||||
| 	.mask_invert		= true, | ||||
| 	.num_regs		= 3, | ||||
| 	.irqs			= max77836_muic_irqs, | ||||
| 	.num_irqs		= ARRAY_SIZE(max77836_muic_irqs), | ||||
| }; | ||||
| 
 | ||||
| static const struct regmap_irq max77836_pmic_irqs[] = { | ||||
| 	{ .reg_offset = 0, .mask = MAX77836_TOPSYS_INT_T120C_MASK, }, | ||||
| 	{ .reg_offset = 0, .mask = MAX77836_TOPSYS_INT_T140C_MASK, }, | ||||
| }; | ||||
| 
 | ||||
| static const struct regmap_irq_chip max77836_pmic_irq_chip = { | ||||
| 	.name			= "max77836-pmic", | ||||
| 	.status_base		= MAX77836_PMIC_REG_TOPSYS_INT, | ||||
| 	.mask_base		= MAX77836_PMIC_REG_TOPSYS_INT_MASK, | ||||
| 	.mask_invert		= false, | ||||
| 	.num_regs		= 1, | ||||
| 	.irqs			= max77836_pmic_irqs, | ||||
| 	.num_irqs		= ARRAY_SIZE(max77836_pmic_irqs), | ||||
| }; | ||||
| 
 | ||||
| static void max14577_print_dev_type(struct max14577 *max14577) | ||||
| { | ||||
| 	u8 reg_data, vendor_id, device_id; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	ret = max14577_read_reg(max14577->regmap, MAX14577_REG_DEVICEID, | ||||
| 			®_data); | ||||
| 	if (ret) { | ||||
| 		dev_err(max14577->dev, | ||||
| 			"Failed to read DEVICEID register: %d\n", ret); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	vendor_id = ((reg_data & DEVID_VENDORID_MASK) >> | ||||
| 				DEVID_VENDORID_SHIFT); | ||||
| 	device_id = ((reg_data & DEVID_DEVICEID_MASK) >> | ||||
| 				DEVID_DEVICEID_SHIFT); | ||||
| 
 | ||||
| 	dev_info(max14577->dev, "Device type: %u (ID: 0x%x, vendor: 0x%x)\n", | ||||
| 			max14577->dev_type, device_id, vendor_id); | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Max77836 specific initialization code for driver probe. | ||||
|  * Adds new I2C dummy device, regmap and regmap IRQ chip. | ||||
|  * Unmasks Interrupt Source register. | ||||
|  * | ||||
|  * On success returns 0. | ||||
|  * On failure returns errno and reverts any changes done so far (e.g. remove | ||||
|  * I2C dummy device), except masking the INT SRC register. | ||||
|  */ | ||||
| static int max77836_init(struct max14577 *max14577) | ||||
| { | ||||
| 	int ret; | ||||
| 	u8 intsrc_mask; | ||||
| 
 | ||||
| 	max14577->i2c_pmic = i2c_new_dummy(max14577->i2c->adapter, | ||||
| 			I2C_ADDR_PMIC); | ||||
| 	if (!max14577->i2c_pmic) { | ||||
| 		dev_err(max14577->dev, "Failed to register PMIC I2C device\n"); | ||||
| 		return -ENODEV; | ||||
| 	} | ||||
| 	i2c_set_clientdata(max14577->i2c_pmic, max14577); | ||||
| 
 | ||||
| 	max14577->regmap_pmic = devm_regmap_init_i2c(max14577->i2c_pmic, | ||||
| 			&max77836_pmic_regmap_config); | ||||
| 	if (IS_ERR(max14577->regmap_pmic)) { | ||||
| 		ret = PTR_ERR(max14577->regmap_pmic); | ||||
| 		dev_err(max14577->dev, "Failed to allocate PMIC register map: %d\n", | ||||
| 				ret); | ||||
| 		goto err; | ||||
| 	} | ||||
| 
 | ||||
| 	/* Un-mask MAX77836 Interrupt Source register */ | ||||
| 	ret = max14577_read_reg(max14577->regmap_pmic, | ||||
| 			MAX77836_PMIC_REG_INTSRC_MASK, &intsrc_mask); | ||||
| 	if (ret < 0) { | ||||
| 		dev_err(max14577->dev, "Failed to read PMIC register\n"); | ||||
| 		goto err; | ||||
| 	} | ||||
| 
 | ||||
| 	intsrc_mask &= ~(MAX77836_INTSRC_MASK_TOP_INT_MASK); | ||||
| 	intsrc_mask &= ~(MAX77836_INTSRC_MASK_MUIC_CHG_INT_MASK); | ||||
| 	ret = max14577_write_reg(max14577->regmap_pmic, | ||||
| 			MAX77836_PMIC_REG_INTSRC_MASK, intsrc_mask); | ||||
| 	if (ret < 0) { | ||||
| 		dev_err(max14577->dev, "Failed to write PMIC register\n"); | ||||
| 		goto err; | ||||
| 	} | ||||
| 
 | ||||
| 	ret = regmap_add_irq_chip(max14577->regmap_pmic, max14577->irq, | ||||
| 			IRQF_TRIGGER_FALLING | IRQF_ONESHOT | IRQF_SHARED, | ||||
| 			0, &max77836_pmic_irq_chip, | ||||
| 			&max14577->irq_data_pmic); | ||||
| 	if (ret != 0) { | ||||
| 		dev_err(max14577->dev, "Failed to request PMIC IRQ %d: %d\n", | ||||
| 				max14577->irq, ret); | ||||
| 		goto err; | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| 
 | ||||
| err: | ||||
| 	i2c_unregister_device(max14577->i2c_pmic); | ||||
| 
 | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Max77836 specific de-initialization code for driver remove. | ||||
|  */ | ||||
| static void max77836_remove(struct max14577 *max14577) | ||||
| { | ||||
| 	regmap_del_irq_chip(max14577->irq, max14577->irq_data_pmic); | ||||
| 	i2c_unregister_device(max14577->i2c_pmic); | ||||
| } | ||||
| 
 | ||||
| static int max14577_i2c_probe(struct i2c_client *i2c, | ||||
| 			      const struct i2c_device_id *id) | ||||
| { | ||||
| 	struct max14577 *max14577; | ||||
| 	struct max14577_platform_data *pdata = dev_get_platdata(&i2c->dev); | ||||
| 	struct device_node *np = i2c->dev.of_node; | ||||
| 	u8 reg_data; | ||||
| 	int ret = 0; | ||||
| 	const struct regmap_irq_chip *irq_chip; | ||||
| 	struct mfd_cell *mfd_devs; | ||||
| 	unsigned int mfd_devs_size; | ||||
| 	int irq_flags; | ||||
| 
 | ||||
| 	if (np) { | ||||
| 		pdata = devm_kzalloc(&i2c->dev, sizeof(*pdata), GFP_KERNEL); | ||||
|  | @ -113,7 +317,8 @@ static int max14577_i2c_probe(struct i2c_client *i2c, | |||
| 	max14577->i2c = i2c; | ||||
| 	max14577->irq = i2c->irq; | ||||
| 
 | ||||
| 	max14577->regmap = devm_regmap_init_i2c(i2c, &max14577_regmap_config); | ||||
| 	max14577->regmap = devm_regmap_init_i2c(i2c, | ||||
| 			&max14577_muic_regmap_config); | ||||
| 	if (IS_ERR(max14577->regmap)) { | ||||
| 		ret = PTR_ERR(max14577->regmap); | ||||
| 		dev_err(max14577->dev, "Failed to allocate register map: %d\n", | ||||
|  | @ -121,23 +326,36 @@ static int max14577_i2c_probe(struct i2c_client *i2c, | |||
| 		return ret; | ||||
| 	} | ||||
| 
 | ||||
| 	ret = max14577_read_reg(max14577->regmap, MAX14577_REG_DEVICEID, | ||||
| 			®_data); | ||||
| 	if (ret) { | ||||
| 		dev_err(max14577->dev, "Device not found on this channel: %d\n", | ||||
| 				ret); | ||||
| 		return ret; | ||||
| 	if (np) { | ||||
| 		const struct of_device_id *of_id; | ||||
| 
 | ||||
| 		of_id = of_match_device(max14577_dt_match, &i2c->dev); | ||||
| 		if (of_id) | ||||
| 			max14577->dev_type = (unsigned int)of_id->data; | ||||
| 	} else { | ||||
| 		max14577->dev_type = id->driver_data; | ||||
| 	} | ||||
| 
 | ||||
| 	max14577_print_dev_type(max14577); | ||||
| 
 | ||||
| 	switch (max14577->dev_type) { | ||||
| 	case MAXIM_DEVICE_TYPE_MAX77836: | ||||
| 		irq_chip = &max77836_muic_irq_chip; | ||||
| 		mfd_devs = max77836_devs; | ||||
| 		mfd_devs_size = ARRAY_SIZE(max77836_devs); | ||||
| 		irq_flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT | IRQF_SHARED; | ||||
| 		break; | ||||
| 	case MAXIM_DEVICE_TYPE_MAX14577: | ||||
| 	default: | ||||
| 		irq_chip = &max14577_irq_chip; | ||||
| 		mfd_devs = max14577_devs; | ||||
| 		mfd_devs_size = ARRAY_SIZE(max14577_devs); | ||||
| 		irq_flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT; | ||||
| 		break; | ||||
| 	} | ||||
| 	max14577->vendor_id = ((reg_data & DEVID_VENDORID_MASK) >> | ||||
| 				DEVID_VENDORID_SHIFT); | ||||
| 	max14577->device_id = ((reg_data & DEVID_DEVICEID_MASK) >> | ||||
| 				DEVID_DEVICEID_SHIFT); | ||||
| 	dev_info(max14577->dev, "Device ID: 0x%x, vendor: 0x%x\n", | ||||
| 			max14577->device_id, max14577->vendor_id); | ||||
| 
 | ||||
| 	ret = regmap_add_irq_chip(max14577->regmap, max14577->irq, | ||||
| 				  IRQF_TRIGGER_FALLING | IRQF_ONESHOT, 0, | ||||
| 				  &max14577_irq_chip, | ||||
| 				  irq_flags, 0, irq_chip, | ||||
| 				  &max14577->irq_data); | ||||
| 	if (ret != 0) { | ||||
| 		dev_err(&i2c->dev, "Failed to request IRQ %d: %d\n", | ||||
|  | @ -145,8 +363,15 @@ static int max14577_i2c_probe(struct i2c_client *i2c, | |||
| 		return ret; | ||||
| 	} | ||||
| 
 | ||||
| 	ret = mfd_add_devices(max14577->dev, -1, max14577_devs, | ||||
| 			ARRAY_SIZE(max14577_devs), NULL, 0, | ||||
| 	/* Max77836 specific initialization code (additional regmap) */ | ||||
| 	if (max14577->dev_type == MAXIM_DEVICE_TYPE_MAX77836) { | ||||
| 		ret = max77836_init(max14577); | ||||
| 		if (ret < 0) | ||||
| 			goto err_max77836; | ||||
| 	} | ||||
| 
 | ||||
| 	ret = mfd_add_devices(max14577->dev, -1, mfd_devs, | ||||
| 			mfd_devs_size, NULL, 0, | ||||
| 			regmap_irq_get_domain(max14577->irq_data)); | ||||
| 	if (ret < 0) | ||||
| 		goto err_mfd; | ||||
|  | @ -156,6 +381,9 @@ static int max14577_i2c_probe(struct i2c_client *i2c, | |||
| 	return 0; | ||||
| 
 | ||||
| err_mfd: | ||||
| 	if (max14577->dev_type == MAXIM_DEVICE_TYPE_MAX77836) | ||||
| 		max77836_remove(max14577); | ||||
| err_max77836: | ||||
| 	regmap_del_irq_chip(max14577->irq, max14577->irq_data); | ||||
| 
 | ||||
| 	return ret; | ||||
|  | @ -167,12 +395,15 @@ static int max14577_i2c_remove(struct i2c_client *i2c) | |||
| 
 | ||||
| 	mfd_remove_devices(max14577->dev); | ||||
| 	regmap_del_irq_chip(max14577->irq, max14577->irq_data); | ||||
| 	if (max14577->dev_type == MAXIM_DEVICE_TYPE_MAX77836) | ||||
| 		max77836_remove(max14577); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static const struct i2c_device_id max14577_i2c_id[] = { | ||||
| 	{ "max14577", 0 }, | ||||
| 	{ "max14577", MAXIM_DEVICE_TYPE_MAX14577, }, | ||||
| 	{ "max77836", MAXIM_DEVICE_TYPE_MAX77836, }, | ||||
| 	{ } | ||||
| }; | ||||
| MODULE_DEVICE_TABLE(i2c, max14577_i2c_id); | ||||
|  | @ -215,11 +446,6 @@ static int max14577_resume(struct device *dev) | |||
| } | ||||
| #endif /* CONFIG_PM_SLEEP */ | ||||
| 
 | ||||
| static struct of_device_id max14577_dt_match[] = { | ||||
| 	{ .compatible = "maxim,max14577", }, | ||||
| 	{}, | ||||
| }; | ||||
| 
 | ||||
| static SIMPLE_DEV_PM_OPS(max14577_pm, max14577_suspend, max14577_resume); | ||||
| 
 | ||||
| static struct i2c_driver max14577_i2c_driver = { | ||||
|  | @ -236,6 +462,9 @@ static struct i2c_driver max14577_i2c_driver = { | |||
| 
 | ||||
| static int __init max14577_i2c_init(void) | ||||
| { | ||||
| 	BUILD_BUG_ON(ARRAY_SIZE(max14577_i2c_id) != MAXIM_DEVICE_TYPE_NUM); | ||||
| 	BUILD_BUG_ON(ARRAY_SIZE(max14577_dt_match) != MAXIM_DEVICE_TYPE_NUM); | ||||
| 
 | ||||
| 	return i2c_add_driver(&max14577_i2c_driver); | ||||
| } | ||||
| subsys_initcall(max14577_i2c_init); | ||||
|  | @ -247,5 +476,5 @@ static void __exit max14577_i2c_exit(void) | |||
| module_exit(max14577_i2c_exit); | ||||
| 
 | ||||
| MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>, Krzysztof Kozlowski <k.kozlowski@samsung.com>"); | ||||
| MODULE_DESCRIPTION("MAXIM 14577 multi-function core driver"); | ||||
| MODULE_DESCRIPTION("Maxim 14577/77836 multi-function core driver"); | ||||
| MODULE_LICENSE("GPL"); | ||||
|  |  | |||
|  | @ -54,6 +54,7 @@ config AD525X_DPOT_SPI | |||
| config ATMEL_PWM | ||||
| 	tristate "Atmel AT32/AT91 PWM support" | ||||
| 	depends on HAVE_CLK | ||||
| 	depends on AVR32 || AT91SAM9263 || AT91SAM9RL || AT91SAM9G45 | ||||
| 	help | ||||
| 	  This option enables device driver support for the PWM channels | ||||
| 	  on certain Atmel processors.  Pulse Width Modulation is used for | ||||
|  | @ -200,7 +201,7 @@ config ICS932S401 | |||
| 
 | ||||
| config ATMEL_SSC | ||||
| 	tristate "Device driver for Atmel SSC peripheral" | ||||
| 	depends on HAS_IOMEM | ||||
| 	depends on HAS_IOMEM && (AVR32 || ARCH_AT91 || COMPILE_TEST) | ||||
| 	---help--- | ||||
| 	  This option enables device driver support for Atmel Synchronized | ||||
| 	  Serial Communication peripheral (SSC). | ||||
|  | @ -468,7 +469,7 @@ config BMP085_SPI | |||
| config PCH_PHUB | ||||
| 	tristate "Intel EG20T PCH/LAPIS Semicon IOH(ML7213/ML7223/ML7831) PHUB" | ||||
| 	select GENERIC_NET_UTILS | ||||
| 	depends on PCI | ||||
| 	depends on PCI && (X86_32 || COMPILE_TEST) | ||||
| 	help | ||||
| 	  This driver is for PCH(Platform controller Hub) PHUB(Packet Hub) of | ||||
| 	  Intel Topcliff which is an IOH(Input/Output Hub) for x86 embedded | ||||
|  |  | |||
|  | @ -11,6 +11,7 @@ | |||
| #include <linux/module.h> | ||||
| #include <linux/interrupt.h> | ||||
| #include <linux/platform_device.h> | ||||
| #include <linux/of.h> | ||||
| #include <linux/completion.h> | ||||
| #include <linux/delay.h> | ||||
| #include <linux/io.h> | ||||
|  | @ -366,11 +367,17 @@ static const struct dev_pm_ops charlcd_pm_ops = { | |||
| 	.resume = charlcd_resume, | ||||
| }; | ||||
| 
 | ||||
| static const struct of_device_id charlcd_match[] = { | ||||
| 	{ .compatible = "arm,versatile-lcd", }, | ||||
| 	{} | ||||
| }; | ||||
| 
 | ||||
| static struct platform_driver charlcd_driver = { | ||||
| 	.driver = { | ||||
| 		.name = DRIVERNAME, | ||||
| 		.owner = THIS_MODULE, | ||||
| 		.pm = &charlcd_pm_ops, | ||||
| 		.of_match_table = of_match_ptr(charlcd_match), | ||||
| 	}, | ||||
| 	.remove = __exit_p(charlcd_remove), | ||||
| }; | ||||
|  |  | |||
|  | @ -85,7 +85,6 @@ static ssize_t ds1682_store(struct device *dev, struct device_attribute *attr, | |||
| { | ||||
| 	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); | ||||
| 	struct i2c_client *client = to_i2c_client(dev); | ||||
| 	char *endp; | ||||
| 	u64 val; | ||||
| 	__le32 val_le; | ||||
| 	int rc; | ||||
|  | @ -93,8 +92,8 @@ static ssize_t ds1682_store(struct device *dev, struct device_attribute *attr, | |||
| 	dev_dbg(dev, "ds1682_store() called on %s\n", attr->attr.name); | ||||
| 
 | ||||
| 	/* Decode input */ | ||||
| 	val = simple_strtoull(buf, &endp, 0); | ||||
| 	if (buf == endp) { | ||||
| 	rc = kstrtoull(buf, 0, &val); | ||||
| 	if (rc < 0) { | ||||
| 		dev_dbg(dev, "input string not a number\n"); | ||||
| 		return -EINVAL; | ||||
| 	} | ||||
|  |  | |||
|  | @ -348,7 +348,7 @@ int genwqe_init_debugfs(struct genwqe_dev *cd) | |||
| 	char name[64]; | ||||
| 	unsigned int i; | ||||
| 
 | ||||
| 	sprintf(card_name, "%s%u_card", GENWQE_DEVNAME, cd->card_idx); | ||||
| 	sprintf(card_name, "%s%d_card", GENWQE_DEVNAME, cd->card_idx); | ||||
| 
 | ||||
| 	root = debugfs_create_dir(card_name, cd->debugfs_genwqe); | ||||
| 	if (!root) { | ||||
|  | @ -454,7 +454,7 @@ int genwqe_init_debugfs(struct genwqe_dev *cd) | |||
| 	} | ||||
| 
 | ||||
| 	for (i = 0; i <  GENWQE_MAX_VFS; i++) { | ||||
| 		sprintf(name, "vf%d_jobtimeout_msec", i); | ||||
| 		sprintf(name, "vf%u_jobtimeout_msec", i); | ||||
| 
 | ||||
| 		file = debugfs_create_u32(name, 0666, root, | ||||
| 					  &cd->vf_jobtimeout_msec[i]); | ||||
|  |  | |||
|  | @ -454,7 +454,7 @@ int genwqe_setup_sgl(struct genwqe_dev *cd, struct genwqe_sgl *sgl, | |||
|  */ | ||||
| int genwqe_free_sync_sgl(struct genwqe_dev *cd, struct genwqe_sgl *sgl) | ||||
| { | ||||
| 	int rc; | ||||
| 	int rc = 0; | ||||
| 	struct pci_dev *pci_dev = cd->pci_dev; | ||||
| 
 | ||||
| 	if (sgl->fpage) { | ||||
|  |  | |||
|  | @ -111,8 +111,6 @@ int mei_amthif_host_init(struct mei_device *dev) | |||
| 		return ret; | ||||
| 	} | ||||
| 
 | ||||
| 	cl->state = MEI_FILE_CONNECTING; | ||||
| 
 | ||||
| 	ret = mei_cl_connect(cl, NULL); | ||||
| 
 | ||||
| 	dev->iamthif_state = MEI_IAMTHIF_IDLE; | ||||
|  |  | |||
|  | @ -247,7 +247,7 @@ static int ___mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length, | |||
| 		return id; | ||||
| 
 | ||||
| 	if (length > dev->me_clients[id].props.max_msg_length) | ||||
| 		return -EINVAL; | ||||
| 		return -EFBIG; | ||||
| 
 | ||||
| 	cb = mei_io_cb_init(cl, NULL); | ||||
| 	if (!cb) | ||||
|  | @ -427,8 +427,6 @@ int mei_cl_enable_device(struct mei_cl_device *device) | |||
| 
 | ||||
| 	mutex_lock(&dev->device_lock); | ||||
| 
 | ||||
| 	cl->state = MEI_FILE_CONNECTING; | ||||
| 
 | ||||
| 	err = mei_cl_connect(cl, NULL); | ||||
| 	if (err < 0) { | ||||
| 		mutex_unlock(&dev->device_lock); | ||||
|  |  | |||
|  | @ -18,6 +18,7 @@ | |||
| #include <linux/sched.h> | ||||
| #include <linux/wait.h> | ||||
| #include <linux/delay.h> | ||||
| #include <linux/pm_runtime.h> | ||||
| 
 | ||||
| #include <linux/mei.h> | ||||
| 
 | ||||
|  | @ -415,6 +416,10 @@ void mei_host_client_init(struct work_struct *work) | |||
| 	dev->reset_count = 0; | ||||
| 
 | ||||
| 	mutex_unlock(&dev->device_lock); | ||||
| 
 | ||||
| 	pm_runtime_mark_last_busy(&dev->pdev->dev); | ||||
| 	dev_dbg(&dev->pdev->dev, "rpm: autosuspend\n"); | ||||
| 	pm_runtime_autosuspend(&dev->pdev->dev); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  | @ -425,6 +430,12 @@ void mei_host_client_init(struct work_struct *work) | |||
|  */ | ||||
| bool mei_hbuf_acquire(struct mei_device *dev) | ||||
| { | ||||
| 	if (mei_pg_state(dev) == MEI_PG_ON || | ||||
| 	    dev->pg_event == MEI_PG_EVENT_WAIT) { | ||||
| 		dev_dbg(&dev->pdev->dev, "device is in pg\n"); | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
| 	if (!dev->hbuf_is_ready) { | ||||
| 		dev_dbg(&dev->pdev->dev, "hbuf is not ready\n"); | ||||
| 		return false; | ||||
|  | @ -460,9 +471,18 @@ int mei_cl_disconnect(struct mei_cl *cl) | |||
| 	if (cl->state != MEI_FILE_DISCONNECTING) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	rets = pm_runtime_get(&dev->pdev->dev); | ||||
| 	if (rets < 0 && rets != -EINPROGRESS) { | ||||
| 		pm_runtime_put_noidle(&dev->pdev->dev); | ||||
| 		cl_err(dev, cl, "rpm: get failed %d\n", rets); | ||||
| 		return rets; | ||||
| 	} | ||||
| 
 | ||||
| 	cb = mei_io_cb_init(cl, NULL); | ||||
| 	if (!cb) | ||||
| 		return -ENOMEM; | ||||
| 	if (!cb) { | ||||
| 		rets = -ENOMEM; | ||||
| 		goto free; | ||||
| 	} | ||||
| 
 | ||||
| 	cb->fop_type = MEI_FOP_CLOSE; | ||||
| 	if (mei_hbuf_acquire(dev)) { | ||||
|  | @ -494,8 +514,7 @@ int mei_cl_disconnect(struct mei_cl *cl) | |||
| 			cl_err(dev, cl, "wrong status client disconnect.\n"); | ||||
| 
 | ||||
| 		if (err) | ||||
| 			cl_dbg(dev, cl, "wait failed disconnect err=%08x\n", | ||||
| 					err); | ||||
| 			cl_dbg(dev, cl, "wait failed disconnect err=%d\n", err); | ||||
| 
 | ||||
| 		cl_err(dev, cl, "failed to disconnect from FW client.\n"); | ||||
| 	} | ||||
|  | @ -503,6 +522,10 @@ int mei_cl_disconnect(struct mei_cl *cl) | |||
| 	mei_io_list_flush(&dev->ctrl_rd_list, cl); | ||||
| 	mei_io_list_flush(&dev->ctrl_wr_list, cl); | ||||
| free: | ||||
| 	cl_dbg(dev, cl, "rpm: autosuspend\n"); | ||||
| 	pm_runtime_mark_last_busy(&dev->pdev->dev); | ||||
| 	pm_runtime_put_autosuspend(&dev->pdev->dev); | ||||
| 
 | ||||
| 	mei_io_cb_free(cb); | ||||
| 	return rets; | ||||
| } | ||||
|  | @ -557,6 +580,13 @@ int mei_cl_connect(struct mei_cl *cl, struct file *file) | |||
| 
 | ||||
| 	dev = cl->dev; | ||||
| 
 | ||||
| 	rets = pm_runtime_get(&dev->pdev->dev); | ||||
| 	if (rets < 0 && rets != -EINPROGRESS) { | ||||
| 		pm_runtime_put_noidle(&dev->pdev->dev); | ||||
| 		cl_err(dev, cl, "rpm: get failed %d\n", rets); | ||||
| 		return rets; | ||||
| 	} | ||||
| 
 | ||||
| 	cb = mei_io_cb_init(cl, file); | ||||
| 	if (!cb) { | ||||
| 		rets = -ENOMEM; | ||||
|  | @ -567,6 +597,7 @@ int mei_cl_connect(struct mei_cl *cl, struct file *file) | |||
| 
 | ||||
| 	/* run hbuf acquire last so we don't have to undo */ | ||||
| 	if (!mei_cl_is_other_connecting(cl) && mei_hbuf_acquire(dev)) { | ||||
| 		cl->state = MEI_FILE_CONNECTING; | ||||
| 		if (mei_hbm_cl_connect_req(dev, cl)) { | ||||
| 			rets = -ENODEV; | ||||
| 			goto out; | ||||
|  | @ -596,6 +627,10 @@ int mei_cl_connect(struct mei_cl *cl, struct file *file) | |||
| 	rets = cl->status; | ||||
| 
 | ||||
| out: | ||||
| 	cl_dbg(dev, cl, "rpm: autosuspend\n"); | ||||
| 	pm_runtime_mark_last_busy(&dev->pdev->dev); | ||||
| 	pm_runtime_put_autosuspend(&dev->pdev->dev); | ||||
| 
 | ||||
| 	mei_io_cb_free(cb); | ||||
| 	return rets; | ||||
| } | ||||
|  | @ -713,23 +748,31 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length) | |||
| 		return  -ENOTTY; | ||||
| 	} | ||||
| 
 | ||||
| 	rets = pm_runtime_get(&dev->pdev->dev); | ||||
| 	if (rets < 0 && rets != -EINPROGRESS) { | ||||
| 		pm_runtime_put_noidle(&dev->pdev->dev); | ||||
| 		cl_err(dev, cl, "rpm: get failed %d\n", rets); | ||||
| 		return rets; | ||||
| 	} | ||||
| 
 | ||||
| 	cb = mei_io_cb_init(cl, NULL); | ||||
| 	if (!cb) | ||||
| 		return -ENOMEM; | ||||
| 	if (!cb) { | ||||
| 		rets = -ENOMEM; | ||||
| 		goto out; | ||||
| 	} | ||||
| 
 | ||||
| 	/* always allocate at least client max message */ | ||||
| 	length = max_t(size_t, length, dev->me_clients[i].props.max_msg_length); | ||||
| 	rets = mei_io_cb_alloc_resp_buf(cb, length); | ||||
| 	if (rets) | ||||
| 		goto err; | ||||
| 		goto out; | ||||
| 
 | ||||
| 	cb->fop_type = MEI_FOP_READ; | ||||
| 	if (mei_hbuf_acquire(dev)) { | ||||
| 		if (mei_hbm_cl_flow_control_req(dev, cl)) { | ||||
| 			cl_err(dev, cl, "flow control send failed\n"); | ||||
| 			rets = -ENODEV; | ||||
| 			goto err; | ||||
| 		} | ||||
| 		rets = mei_hbm_cl_flow_control_req(dev, cl); | ||||
| 		if (rets < 0) | ||||
| 			goto out; | ||||
| 
 | ||||
| 		list_add_tail(&cb->list, &dev->read_list.list); | ||||
| 	} else { | ||||
| 		list_add_tail(&cb->list, &dev->ctrl_wr_list.list); | ||||
|  | @ -737,9 +780,14 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length) | |||
| 
 | ||||
| 	cl->read_cb = cb; | ||||
| 
 | ||||
| 	return rets; | ||||
| err: | ||||
| 	mei_io_cb_free(cb); | ||||
| out: | ||||
| 	cl_dbg(dev, cl, "rpm: autosuspend\n"); | ||||
| 	pm_runtime_mark_last_busy(&dev->pdev->dev); | ||||
| 	pm_runtime_put_autosuspend(&dev->pdev->dev); | ||||
| 
 | ||||
| 	if (rets) | ||||
| 		mei_io_cb_free(cb); | ||||
| 
 | ||||
| 	return rets; | ||||
| } | ||||
| 
 | ||||
|  | @ -776,7 +824,7 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, | |||
| 		return rets; | ||||
| 
 | ||||
| 	if (rets == 0) { | ||||
| 		cl_dbg(dev, cl,	"No flow control credentials: not sending.\n"); | ||||
| 		cl_dbg(dev, cl, "No flow control credentials: not sending.\n"); | ||||
| 		return 0; | ||||
| 	} | ||||
| 
 | ||||
|  | @ -856,6 +904,12 @@ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking) | |||
| 
 | ||||
| 	cl_dbg(dev, cl, "mei_cl_write %d\n", buf->size); | ||||
| 
 | ||||
| 	rets = pm_runtime_get(&dev->pdev->dev); | ||||
| 	if (rets < 0 && rets != -EINPROGRESS) { | ||||
| 		pm_runtime_put_noidle(&dev->pdev->dev); | ||||
| 		cl_err(dev, cl, "rpm: get failed %d\n", rets); | ||||
| 		return rets; | ||||
| 	} | ||||
| 
 | ||||
| 	cb->fop_type = MEI_FOP_WRITE; | ||||
| 	cb->buf_idx = 0; | ||||
|  | @ -926,6 +980,10 @@ out: | |||
| 
 | ||||
| 	rets = buf->size; | ||||
| err: | ||||
| 	cl_dbg(dev, cl, "rpm: autosuspend\n"); | ||||
| 	pm_runtime_mark_last_busy(&dev->pdev->dev); | ||||
| 	pm_runtime_put_autosuspend(&dev->pdev->dev); | ||||
| 
 | ||||
| 	return rets; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -14,10 +14,12 @@ | |||
|  * | ||||
|  */ | ||||
| 
 | ||||
| #include <linux/export.h> | ||||
| #include <linux/pci.h> | ||||
| #include <linux/sched.h> | ||||
| #include <linux/wait.h> | ||||
| #include <linux/mei.h> | ||||
| #include <linux/pm_runtime.h> | ||||
| 
 | ||||
| #include "mei_dev.h" | ||||
| #include "hbm.h" | ||||
|  | @ -57,6 +59,34 @@ static int mei_cl_conn_status_to_errno(enum mei_cl_connect_status status) | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * mei_hbm_idle - set hbm to idle state | ||||
|  * | ||||
|  * @dev: the device structure | ||||
|  */ | ||||
| void mei_hbm_idle(struct mei_device *dev) | ||||
| { | ||||
| 	dev->init_clients_timer = 0; | ||||
| 	dev->hbm_state = MEI_HBM_IDLE; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * mei_hbm_reset - reset hbm counters and book keeping data structurs | ||||
|  * | ||||
|  * @dev: the device structure | ||||
|  */ | ||||
| void mei_hbm_reset(struct mei_device *dev) | ||||
| { | ||||
| 	dev->me_clients_num = 0; | ||||
| 	dev->me_client_presentation_num = 0; | ||||
| 	dev->me_client_index = 0; | ||||
| 
 | ||||
| 	kfree(dev->me_clients); | ||||
| 	dev->me_clients = NULL; | ||||
| 
 | ||||
| 	mei_hbm_idle(dev); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * mei_hbm_me_cl_allocate - allocates storage for me clients | ||||
|  * | ||||
|  | @ -69,9 +99,7 @@ static int mei_hbm_me_cl_allocate(struct mei_device *dev) | |||
| 	struct mei_me_client *clients; | ||||
| 	int b; | ||||
| 
 | ||||
| 	dev->me_clients_num = 0; | ||||
| 	dev->me_client_presentation_num = 0; | ||||
| 	dev->me_client_index = 0; | ||||
| 	mei_hbm_reset(dev); | ||||
| 
 | ||||
| 	/* count how many ME clients we have */ | ||||
| 	for_each_set_bit(b, dev->me_clients_map, MEI_CLIENTS_MAX) | ||||
|  | @ -80,9 +108,6 @@ static int mei_hbm_me_cl_allocate(struct mei_device *dev) | |||
| 	if (dev->me_clients_num == 0) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	kfree(dev->me_clients); | ||||
| 	dev->me_clients = NULL; | ||||
| 
 | ||||
| 	dev_dbg(&dev->pdev->dev, "memory allocation for ME clients size=%ld.\n", | ||||
| 		dev->me_clients_num * sizeof(struct mei_me_client)); | ||||
| 	/* allocate storage for ME clients representation */ | ||||
|  | @ -133,17 +158,6 @@ bool mei_hbm_cl_addr_equal(struct mei_cl *cl, void *buf) | |||
| } | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * mei_hbm_idle - set hbm to idle state | ||||
|  * | ||||
|  * @dev: the device structure | ||||
|  */ | ||||
| void mei_hbm_idle(struct mei_device *dev) | ||||
| { | ||||
| 	dev->init_clients_timer = 0; | ||||
| 	dev->hbm_state = MEI_HBM_IDLE; | ||||
| } | ||||
| 
 | ||||
| int mei_hbm_start_wait(struct mei_device *dev) | ||||
| { | ||||
| 	int ret; | ||||
|  | @ -289,6 +303,34 @@ static int mei_hbm_prop_req(struct mei_device *dev) | |||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * mei_hbm_pg - sends pg command | ||||
|  * | ||||
|  * @dev: the device structure | ||||
|  * @pg_cmd: the pg command code | ||||
|  * | ||||
|  * This function returns -EIO on write failure | ||||
|  */ | ||||
| int mei_hbm_pg(struct mei_device *dev, u8 pg_cmd) | ||||
| { | ||||
| 	struct mei_msg_hdr *mei_hdr = &dev->wr_msg.hdr; | ||||
| 	struct hbm_power_gate *req; | ||||
| 	const size_t len = sizeof(struct hbm_power_gate); | ||||
| 	int ret; | ||||
| 
 | ||||
| 	mei_hbm_hdr(mei_hdr, len); | ||||
| 
 | ||||
| 	req = (struct hbm_power_gate *)dev->wr_msg.data; | ||||
| 	memset(req, 0, len); | ||||
| 	req->hbm_cmd = pg_cmd; | ||||
| 
 | ||||
| 	ret = mei_write_message(dev, mei_hdr, dev->wr_msg.data); | ||||
| 	if (ret) | ||||
| 		dev_err(&dev->pdev->dev, "power gate command write failed.\n"); | ||||
| 	return ret; | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(mei_hbm_pg); | ||||
| 
 | ||||
| /**
 | ||||
|  * mei_hbm_stop_req - send stop request message | ||||
|  * | ||||
|  | @ -701,6 +743,27 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) | |||
| 		mei_hbm_cl_flow_control_res(dev, flow_control); | ||||
| 		break; | ||||
| 
 | ||||
| 	case MEI_PG_ISOLATION_ENTRY_RES_CMD: | ||||
| 		dev_dbg(&dev->pdev->dev, "power gate isolation entry response received\n"); | ||||
| 		dev->pg_event = MEI_PG_EVENT_RECEIVED; | ||||
| 		if (waitqueue_active(&dev->wait_pg)) | ||||
| 			wake_up(&dev->wait_pg); | ||||
| 		break; | ||||
| 
 | ||||
| 	case MEI_PG_ISOLATION_EXIT_REQ_CMD: | ||||
| 		dev_dbg(&dev->pdev->dev, "power gate isolation exit request received\n"); | ||||
| 		dev->pg_event = MEI_PG_EVENT_RECEIVED; | ||||
| 		if (waitqueue_active(&dev->wait_pg)) | ||||
| 			wake_up(&dev->wait_pg); | ||||
| 		else | ||||
| 			/*
 | ||||
| 			* If the driver is not waiting on this then | ||||
| 			* this is HW initiated exit from PG. | ||||
| 			* Start runtime pm resume sequence to exit from PG. | ||||
| 			*/ | ||||
| 			pm_request_resume(&dev->pdev->dev); | ||||
| 		break; | ||||
| 
 | ||||
| 	case HOST_CLIENT_PROPERTIES_RES_CMD: | ||||
| 		dev_dbg(&dev->pdev->dev, "hbm: properties response: message received.\n"); | ||||
| 
 | ||||
|  |  | |||
|  | @ -50,6 +50,7 @@ static inline void mei_hbm_hdr(struct mei_msg_hdr *hdr, size_t length) | |||
| } | ||||
| 
 | ||||
| void mei_hbm_idle(struct mei_device *dev); | ||||
| void mei_hbm_reset(struct mei_device *dev); | ||||
| int mei_hbm_start_req(struct mei_device *dev); | ||||
| int mei_hbm_start_wait(struct mei_device *dev); | ||||
| int mei_hbm_cl_flow_control_req(struct mei_device *dev, struct mei_cl *cl); | ||||
|  | @ -57,6 +58,7 @@ int mei_hbm_cl_disconnect_req(struct mei_device *dev, struct mei_cl *cl); | |||
| int mei_hbm_cl_disconnect_rsp(struct mei_device *dev, struct mei_cl *cl); | ||||
| int mei_hbm_cl_connect_req(struct mei_device *dev, struct mei_cl *cl); | ||||
| bool mei_hbm_version_is_supported(struct mei_device *dev); | ||||
| int mei_hbm_pg(struct mei_device *dev, u8 pg_cmd); | ||||
| 
 | ||||
| #endif /* _MEI_HBM_H_ */ | ||||
| 
 | ||||
|  |  | |||
|  | @ -133,6 +133,8 @@ | |||
| #define ME_CB_RW   8 | ||||
| /* ME_CSR_HA - ME Control Status Host Access register (read only) */ | ||||
| #define ME_CSR_HA  0xC | ||||
| /* H_HGC_CSR - PGI register */ | ||||
| #define H_HPG_CSR  0x10 | ||||
| 
 | ||||
| 
 | ||||
| /* register bits of H_CSR (Host Control Status register) */ | ||||
|  | @ -162,6 +164,8 @@ access to ME_CBD */ | |||
| #define ME_CBWP_HRA       0x00FF0000 | ||||
| /* ME CB Read Pointer HRA - host read only access to ME_CBRP */ | ||||
| #define ME_CBRP_HRA       0x0000FF00 | ||||
| /* ME Power Gate Isolation Capability HRA  - host ready only access */ | ||||
| #define ME_PGIC_HRA       0x00000040 | ||||
| /* ME Reset HRA - host read only access to ME_RST */ | ||||
| #define ME_RST_HRA        0x00000010 | ||||
| /* ME Ready HRA - host read only access to ME_RDY */ | ||||
|  | @ -173,4 +177,9 @@ access to ME_CBD */ | |||
| /* ME Interrupt Enable HRA - host read only access to ME_IE */ | ||||
| #define ME_IE_HRA         0x00000001 | ||||
| 
 | ||||
| 
 | ||||
| /* register bits - H_HPG_CSR */ | ||||
| #define H_HPG_CSR_PGIHEXR       0x00000001 | ||||
| #define H_HPG_CSR_PGI           0x00000002 | ||||
| 
 | ||||
| #endif /* _MEI_HW_MEI_REGS_H_ */ | ||||
|  |  | |||
|  | @ -109,10 +109,27 @@ static inline void mei_hcsr_set(struct mei_me_hw *hw, u32 hcsr) | |||
|  */ | ||||
| static void mei_me_hw_config(struct mei_device *dev) | ||||
| { | ||||
| 	struct mei_me_hw *hw = to_me_hw(dev); | ||||
| 	u32 hcsr = mei_hcsr_read(to_me_hw(dev)); | ||||
| 	/* Doesn't change in runtime */ | ||||
| 	dev->hbuf_depth = (hcsr & H_CBD) >> 24; | ||||
| 
 | ||||
| 	hw->pg_state = MEI_PG_OFF; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * mei_me_pg_state  - translate internal pg state | ||||
|  *   to the mei power gating state | ||||
|  * | ||||
|  * @hw -  me hardware | ||||
|  * returns: MEI_PG_OFF if aliveness is on and MEI_PG_ON otherwise | ||||
|  */ | ||||
| static inline enum mei_pg_state mei_me_pg_state(struct mei_device *dev) | ||||
| { | ||||
| 	struct mei_me_hw *hw = to_me_hw(dev); | ||||
| 	return hw->pg_state; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * mei_clear_interrupts - clear and stop interrupts | ||||
|  * | ||||
|  | @ -164,6 +181,9 @@ static void mei_me_hw_reset_release(struct mei_device *dev) | |||
| 	hcsr |= H_IG; | ||||
| 	hcsr &= ~H_RST; | ||||
| 	mei_hcsr_set(hw, hcsr); | ||||
| 
 | ||||
| 	/* complete this write before we set host ready on another CPU */ | ||||
| 	mmiowb(); | ||||
| } | ||||
| /**
 | ||||
|  * mei_me_hw_reset - resets fw via mei csr register. | ||||
|  | @ -183,8 +203,21 @@ static int mei_me_hw_reset(struct mei_device *dev, bool intr_enable) | |||
| 	else | ||||
| 		hcsr &= ~H_IE; | ||||
| 
 | ||||
| 	dev->recvd_hw_ready = false; | ||||
| 	mei_me_reg_write(hw, H_CSR, hcsr); | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Host reads the H_CSR once to ensure that the | ||||
| 	 * posted write to H_CSR completes. | ||||
| 	 */ | ||||
| 	hcsr = mei_hcsr_read(hw); | ||||
| 
 | ||||
| 	if ((hcsr & H_RST) == 0) | ||||
| 		dev_warn(&dev->pdev->dev, "H_RST is not set = 0x%08X", hcsr); | ||||
| 
 | ||||
| 	if ((hcsr & H_RDY) == H_RDY) | ||||
| 		dev_warn(&dev->pdev->dev, "H_RDY is not cleared 0x%08X", hcsr); | ||||
| 
 | ||||
| 	if (intr_enable == false) | ||||
| 		mei_me_hw_reset_release(dev); | ||||
| 
 | ||||
|  | @ -201,6 +234,7 @@ static int mei_me_hw_reset(struct mei_device *dev, bool intr_enable) | |||
| static void mei_me_host_set_ready(struct mei_device *dev) | ||||
| { | ||||
| 	struct mei_me_hw *hw = to_me_hw(dev); | ||||
| 	hw->host_hw_state = mei_hcsr_read(hw); | ||||
| 	hw->host_hw_state |= H_IE | H_IG | H_RDY; | ||||
| 	mei_hcsr_set(hw, hw->host_hw_state); | ||||
| } | ||||
|  | @ -233,10 +267,7 @@ static bool mei_me_hw_is_ready(struct mei_device *dev) | |||
| static int mei_me_hw_ready_wait(struct mei_device *dev) | ||||
| { | ||||
| 	int err; | ||||
| 	if (mei_me_hw_is_ready(dev)) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	dev->recvd_hw_ready = false; | ||||
| 	mutex_unlock(&dev->device_lock); | ||||
| 	err = wait_event_interruptible_timeout(dev->wait_hw_ready, | ||||
| 			dev->recvd_hw_ready, | ||||
|  | @ -430,6 +461,144 @@ static int mei_me_read_slots(struct mei_device *dev, unsigned char *buffer, | |||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * mei_me_pg_enter - write pg enter register to mei device. | ||||
|  * | ||||
|  * @dev: the device structure | ||||
|  */ | ||||
| static void mei_me_pg_enter(struct mei_device *dev) | ||||
| { | ||||
| 	struct mei_me_hw *hw = to_me_hw(dev); | ||||
| 	u32 reg = mei_me_reg_read(hw, H_HPG_CSR); | ||||
| 	reg |= H_HPG_CSR_PGI; | ||||
| 	mei_me_reg_write(hw, H_HPG_CSR, reg); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * mei_me_pg_enter - write pg enter register to mei device. | ||||
|  * | ||||
|  * @dev: the device structure | ||||
|  */ | ||||
| static void mei_me_pg_exit(struct mei_device *dev) | ||||
| { | ||||
| 	struct mei_me_hw *hw = to_me_hw(dev); | ||||
| 	u32 reg = mei_me_reg_read(hw, H_HPG_CSR); | ||||
| 
 | ||||
| 	WARN(!(reg & H_HPG_CSR_PGI), "PGI is not set\n"); | ||||
| 
 | ||||
| 	reg |= H_HPG_CSR_PGIHEXR; | ||||
| 	mei_me_reg_write(hw, H_HPG_CSR, reg); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * mei_me_pg_set_sync - perform pg entry procedure | ||||
|  * | ||||
|  * @dev: the device structure | ||||
|  * | ||||
|  * returns 0 on success an error code otherwise | ||||
|  */ | ||||
| int mei_me_pg_set_sync(struct mei_device *dev) | ||||
| { | ||||
| 	struct mei_me_hw *hw = to_me_hw(dev); | ||||
| 	unsigned long timeout = mei_secs_to_jiffies(MEI_PGI_TIMEOUT); | ||||
| 	int ret; | ||||
| 
 | ||||
| 	dev->pg_event = MEI_PG_EVENT_WAIT; | ||||
| 
 | ||||
| 	ret = mei_hbm_pg(dev, MEI_PG_ISOLATION_ENTRY_REQ_CMD); | ||||
| 	if (ret) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	mutex_unlock(&dev->device_lock); | ||||
| 	wait_event_timeout(dev->wait_pg, | ||||
| 		dev->pg_event == MEI_PG_EVENT_RECEIVED, timeout); | ||||
| 	mutex_lock(&dev->device_lock); | ||||
| 
 | ||||
| 	if (dev->pg_event == MEI_PG_EVENT_RECEIVED) { | ||||
| 		mei_me_pg_enter(dev); | ||||
| 		ret = 0; | ||||
| 	} else { | ||||
| 		ret = -ETIME; | ||||
| 	} | ||||
| 
 | ||||
| 	dev->pg_event = MEI_PG_EVENT_IDLE; | ||||
| 	hw->pg_state = MEI_PG_ON; | ||||
| 
 | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * mei_me_pg_unset_sync - perform pg exit procedure | ||||
|  * | ||||
|  * @dev: the device structure | ||||
|  * | ||||
|  * returns 0 on success an error code otherwise | ||||
|  */ | ||||
| int mei_me_pg_unset_sync(struct mei_device *dev) | ||||
| { | ||||
| 	struct mei_me_hw *hw = to_me_hw(dev); | ||||
| 	unsigned long timeout = mei_secs_to_jiffies(MEI_PGI_TIMEOUT); | ||||
| 	int ret; | ||||
| 
 | ||||
| 	if (dev->pg_event == MEI_PG_EVENT_RECEIVED) | ||||
| 		goto reply; | ||||
| 
 | ||||
| 	dev->pg_event = MEI_PG_EVENT_WAIT; | ||||
| 
 | ||||
| 	mei_me_pg_exit(dev); | ||||
| 
 | ||||
| 	mutex_unlock(&dev->device_lock); | ||||
| 	wait_event_timeout(dev->wait_pg, | ||||
| 		dev->pg_event == MEI_PG_EVENT_RECEIVED, timeout); | ||||
| 	mutex_lock(&dev->device_lock); | ||||
| 
 | ||||
| reply: | ||||
| 	if (dev->pg_event == MEI_PG_EVENT_RECEIVED) | ||||
| 		ret = mei_hbm_pg(dev, MEI_PG_ISOLATION_EXIT_RES_CMD); | ||||
| 	else | ||||
| 		ret = -ETIME; | ||||
| 
 | ||||
| 	dev->pg_event = MEI_PG_EVENT_IDLE; | ||||
| 	hw->pg_state = MEI_PG_OFF; | ||||
| 
 | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * mei_me_pg_is_enabled - detect if PG is supported by HW | ||||
|  * | ||||
|  * @dev: the device structure | ||||
|  * | ||||
|  * returns: true is pg supported, false otherwise | ||||
|  */ | ||||
| static bool mei_me_pg_is_enabled(struct mei_device *dev) | ||||
| { | ||||
| 	struct mei_me_hw *hw = to_me_hw(dev); | ||||
| 	u32 reg = mei_me_reg_read(hw, ME_CSR_HA); | ||||
| 
 | ||||
| 	if ((reg & ME_PGIC_HRA) == 0) | ||||
| 		goto notsupported; | ||||
| 
 | ||||
| 	if (dev->version.major_version < HBM_MAJOR_VERSION_PGI) | ||||
| 		goto notsupported; | ||||
| 
 | ||||
| 	if (dev->version.major_version == HBM_MAJOR_VERSION_PGI && | ||||
| 	    dev->version.minor_version < HBM_MINOR_VERSION_PGI) | ||||
| 		goto notsupported; | ||||
| 
 | ||||
| 	return true; | ||||
| 
 | ||||
| notsupported: | ||||
| 	dev_dbg(&dev->pdev->dev, "pg: not supported: HGP = %d hbm version %d.%d ?= %d.%d\n", | ||||
| 		!!(reg & ME_PGIC_HRA), | ||||
| 		dev->version.major_version, | ||||
| 		dev->version.minor_version, | ||||
| 		HBM_MAJOR_VERSION_PGI, | ||||
| 		HBM_MINOR_VERSION_PGI); | ||||
| 
 | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * mei_me_irq_quick_handler - The ISR of the MEI device | ||||
|  * | ||||
|  | @ -491,14 +660,13 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id) | |||
| 	/*  check if we need to start the dev */ | ||||
| 	if (!mei_host_is_ready(dev)) { | ||||
| 		if (mei_hw_is_ready(dev)) { | ||||
| 			mei_me_hw_reset_release(dev); | ||||
| 			dev_dbg(&dev->pdev->dev, "we need to start the dev.\n"); | ||||
| 
 | ||||
| 			dev->recvd_hw_ready = true; | ||||
| 			wake_up_interruptible(&dev->wait_hw_ready); | ||||
| 		} else { | ||||
| 
 | ||||
| 			dev_dbg(&dev->pdev->dev, "Reset Completed.\n"); | ||||
| 			mei_me_hw_reset_release(dev); | ||||
| 			dev_dbg(&dev->pdev->dev, "Spurious Interrupt\n"); | ||||
| 		} | ||||
| 		goto end; | ||||
| 	} | ||||
|  | @ -524,9 +692,15 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id) | |||
| 
 | ||||
| 	dev->hbuf_is_ready = mei_hbuf_is_ready(dev); | ||||
| 
 | ||||
| 	rets = mei_irq_write_handler(dev, &complete_list); | ||||
| 
 | ||||
| 	dev->hbuf_is_ready = mei_hbuf_is_ready(dev); | ||||
| 	/*
 | ||||
| 	 * During PG handshake only allowed write is the replay to the | ||||
| 	 * PG exit message, so block calling write function | ||||
| 	 * if the pg state is not idle | ||||
| 	 */ | ||||
| 	if (dev->pg_event == MEI_PG_EVENT_IDLE) { | ||||
| 		rets = mei_irq_write_handler(dev, &complete_list); | ||||
| 		dev->hbuf_is_ready = mei_hbuf_is_ready(dev); | ||||
| 	} | ||||
| 
 | ||||
| 	mei_irq_compl_handler(dev, &complete_list); | ||||
| 
 | ||||
|  | @ -535,8 +709,65 @@ end: | |||
| 	mutex_unlock(&dev->device_lock); | ||||
| 	return IRQ_HANDLED; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * mei_me_fw_status - retrieve fw status from the pci config space | ||||
|  * | ||||
|  * @dev: the device structure | ||||
|  * @fw_status: fw status registers storage | ||||
|  * | ||||
|  * returns 0 on success an error code otherwise | ||||
|  */ | ||||
| static int mei_me_fw_status(struct mei_device *dev, | ||||
| 			    struct mei_fw_status *fw_status) | ||||
| { | ||||
| 	const u32 pci_cfg_reg[] = {PCI_CFG_HFS_1, PCI_CFG_HFS_2}; | ||||
| 	int i; | ||||
| 
 | ||||
| 	if (!fw_status) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	switch (dev->pdev->device) { | ||||
| 	case MEI_DEV_ID_IBXPK_1: | ||||
| 	case MEI_DEV_ID_IBXPK_2: | ||||
| 	case MEI_DEV_ID_CPT_1: | ||||
| 	case MEI_DEV_ID_PBG_1: | ||||
| 	case MEI_DEV_ID_PPT_1: | ||||
| 	case MEI_DEV_ID_PPT_2: | ||||
| 	case MEI_DEV_ID_PPT_3: | ||||
| 	case MEI_DEV_ID_LPT_H: | ||||
| 	case MEI_DEV_ID_LPT_W: | ||||
| 	case MEI_DEV_ID_LPT_LP: | ||||
| 	case MEI_DEV_ID_LPT_HR: | ||||
| 	case MEI_DEV_ID_WPT_LP: | ||||
| 		fw_status->count = 2; | ||||
| 		break; | ||||
| 	case MEI_DEV_ID_ICH10_1: | ||||
| 	case MEI_DEV_ID_ICH10_2: | ||||
| 	case MEI_DEV_ID_ICH10_3: | ||||
| 	case MEI_DEV_ID_ICH10_4: | ||||
| 		fw_status->count = 1; | ||||
| 		break; | ||||
| 	default: | ||||
| 		fw_status->count = 0; | ||||
| 		break; | ||||
| 	} | ||||
| 
 | ||||
| 	for (i = 0; i < fw_status->count && i < MEI_FW_STATUS_MAX; i++) { | ||||
| 		int ret; | ||||
| 		ret = pci_read_config_dword(dev->pdev, | ||||
| 				pci_cfg_reg[i], &fw_status->status[i]); | ||||
| 		if (ret) | ||||
| 			return ret; | ||||
| 	} | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static const struct mei_hw_ops mei_me_hw_ops = { | ||||
| 
 | ||||
| 	.pg_state  = mei_me_pg_state, | ||||
| 
 | ||||
| 	.fw_status = mei_me_fw_status, | ||||
| 	.host_is_ready = mei_me_host_is_ready, | ||||
| 
 | ||||
| 	.hw_is_ready = mei_me_hw_is_ready, | ||||
|  | @ -544,6 +775,8 @@ static const struct mei_hw_ops mei_me_hw_ops = { | |||
| 	.hw_config = mei_me_hw_config, | ||||
| 	.hw_start = mei_me_hw_start, | ||||
| 
 | ||||
| 	.pg_is_enabled = mei_me_pg_is_enabled, | ||||
| 
 | ||||
| 	.intr_clear = mei_me_intr_clear, | ||||
| 	.intr_enable = mei_me_intr_enable, | ||||
| 	.intr_disable = mei_me_intr_disable, | ||||
|  | @ -559,14 +792,81 @@ static const struct mei_hw_ops mei_me_hw_ops = { | |||
| 	.read = mei_me_read_slots | ||||
| }; | ||||
| 
 | ||||
| static bool mei_me_fw_type_nm(struct pci_dev *pdev) | ||||
| { | ||||
| 	u32 reg; | ||||
| 	pci_read_config_dword(pdev, PCI_CFG_HFS_2, ®); | ||||
| 	/* make sure that bit 9 (NM) is up and bit 10 (DM) is down */ | ||||
| 	return (reg & 0x600) == 0x200; | ||||
| } | ||||
| 
 | ||||
| #define MEI_CFG_FW_NM                           \ | ||||
| 	.quirk_probe = mei_me_fw_type_nm | ||||
| 
 | ||||
| static bool mei_me_fw_type_sps(struct pci_dev *pdev) | ||||
| { | ||||
| 	u32 reg; | ||||
| 	/* Read ME FW Status check for SPS Firmware */ | ||||
| 	pci_read_config_dword(pdev, PCI_CFG_HFS_1, ®); | ||||
| 	/* if bits [19:16] = 15, running SPS Firmware */ | ||||
| 	return (reg & 0xf0000) == 0xf0000; | ||||
| } | ||||
| 
 | ||||
| #define MEI_CFG_FW_SPS                           \ | ||||
| 	.quirk_probe = mei_me_fw_type_sps | ||||
| 
 | ||||
| 
 | ||||
| #define MEI_CFG_LEGACY_HFS                      \ | ||||
| 	.fw_status.count = 0 | ||||
| 
 | ||||
| #define MEI_CFG_ICH_HFS                        \ | ||||
| 	.fw_status.count = 1,                   \ | ||||
| 	.fw_status.status[0] = PCI_CFG_HFS_1 | ||||
| 
 | ||||
| #define MEI_CFG_PCH_HFS                         \ | ||||
| 	.fw_status.count = 2,                   \ | ||||
| 	.fw_status.status[0] = PCI_CFG_HFS_1,   \ | ||||
| 	.fw_status.status[1] = PCI_CFG_HFS_2 | ||||
| 
 | ||||
| 
 | ||||
| /* ICH Legacy devices */ | ||||
| const struct mei_cfg mei_me_legacy_cfg = { | ||||
| 	MEI_CFG_LEGACY_HFS, | ||||
| }; | ||||
| 
 | ||||
| /* ICH devices */ | ||||
| const struct mei_cfg mei_me_ich_cfg = { | ||||
| 	MEI_CFG_ICH_HFS, | ||||
| }; | ||||
| 
 | ||||
| /* PCH devices */ | ||||
| const struct mei_cfg mei_me_pch_cfg = { | ||||
| 	MEI_CFG_PCH_HFS, | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| /* PCH Cougar Point and Patsburg with quirk for Node Manager exclusion */ | ||||
| const struct mei_cfg mei_me_pch_cpt_pbg_cfg = { | ||||
| 	MEI_CFG_PCH_HFS, | ||||
| 	MEI_CFG_FW_NM, | ||||
| }; | ||||
| 
 | ||||
| /* PCH Lynx Point with quirk for SPS Firmware exclusion */ | ||||
| const struct mei_cfg mei_me_lpt_cfg = { | ||||
| 	MEI_CFG_PCH_HFS, | ||||
| 	MEI_CFG_FW_SPS, | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * mei_me_dev_init - allocates and initializes the mei device structure | ||||
|  * | ||||
|  * @pdev: The pci device structure | ||||
|  * @cfg: per device generation config | ||||
|  * | ||||
|  * returns The mei_device_device pointer on success, NULL on failure. | ||||
|  */ | ||||
| struct mei_device *mei_me_dev_init(struct pci_dev *pdev) | ||||
| struct mei_device *mei_me_dev_init(struct pci_dev *pdev, | ||||
| 				   const struct mei_cfg *cfg) | ||||
| { | ||||
| 	struct mei_device *dev; | ||||
| 
 | ||||
|  | @ -575,7 +875,7 @@ struct mei_device *mei_me_dev_init(struct pci_dev *pdev) | |||
| 	if (!dev) | ||||
| 		return NULL; | ||||
| 
 | ||||
| 	mei_device_init(dev); | ||||
| 	mei_device_init(dev, cfg); | ||||
| 
 | ||||
| 	dev->ops = &mei_me_hw_ops; | ||||
| 
 | ||||
|  |  | |||
|  | @ -24,6 +24,8 @@ | |||
| #include "mei_dev.h" | ||||
| #include "client.h" | ||||
| 
 | ||||
| #define MEI_ME_RPM_TIMEOUT    500 /* ms */ | ||||
| 
 | ||||
| struct mei_me_hw { | ||||
| 	void __iomem *mem_addr; | ||||
| 	/*
 | ||||
|  | @ -31,11 +33,22 @@ struct mei_me_hw { | |||
| 	 */ | ||||
| 	u32 host_hw_state; | ||||
| 	u32 me_hw_state; | ||||
| 	enum mei_pg_state pg_state; | ||||
| }; | ||||
| 
 | ||||
| #define to_me_hw(dev) (struct mei_me_hw *)((dev)->hw) | ||||
| 
 | ||||
| struct mei_device *mei_me_dev_init(struct pci_dev *pdev); | ||||
| extern const struct mei_cfg mei_me_legacy_cfg; | ||||
| extern const struct mei_cfg mei_me_ich_cfg; | ||||
| extern const struct mei_cfg mei_me_pch_cfg; | ||||
| extern const struct mei_cfg mei_me_pch_cpt_pbg_cfg; | ||||
| extern const struct mei_cfg mei_me_lpt_cfg; | ||||
| 
 | ||||
| struct mei_device *mei_me_dev_init(struct pci_dev *pdev, | ||||
| 				   const struct mei_cfg *cfg); | ||||
| 
 | ||||
| int mei_me_pg_set_sync(struct mei_device *dev); | ||||
| int mei_me_pg_unset_sync(struct mei_device *dev); | ||||
| 
 | ||||
| irqreturn_t mei_me_irq_quick_handler(int irq, void *dev_id); | ||||
| irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id); | ||||
|  |  | |||
|  | @ -89,7 +89,7 @@ enum { | |||
| #  define PCI_CFG_TXE_FW_STS0_ERR_CODE_MSK  0x0000F000 | ||||
| #  define PCI_CFG_TXE_FW_STS0_OP_MODE_MSK   0x000F0000 | ||||
| #  define PCI_CFG_TXE_FW_STS0_RST_CNT_MSK   0x00F00000 | ||||
| 
 | ||||
| #define PCI_CFG_TXE_FW_STS1   0x48 | ||||
| 
 | ||||
| #define IPC_BASE_ADDR	0x80400 /* SeC IPC Base Address */ | ||||
| 
 | ||||
|  |  | |||
|  | @ -158,7 +158,7 @@ static bool mei_txe_aliveness_set(struct mei_device *dev, u32 req) | |||
| 	dev_dbg(&dev->pdev->dev, "Aliveness current=%d request=%d\n", | ||||
| 				hw->aliveness, req); | ||||
| 	if (do_req) { | ||||
| 		hw->recvd_aliveness = false; | ||||
| 		dev->pg_event = MEI_PG_EVENT_WAIT; | ||||
| 		mei_txe_br_reg_write(hw, SICR_HOST_ALIVENESS_REQ_REG, req); | ||||
| 	} | ||||
| 	return do_req; | ||||
|  | @ -213,6 +213,7 @@ static int mei_txe_aliveness_poll(struct mei_device *dev, u32 expected) | |||
| 	do { | ||||
| 		hw->aliveness = mei_txe_aliveness_get(dev); | ||||
| 		if (hw->aliveness == expected) { | ||||
| 			dev->pg_event = MEI_PG_EVENT_IDLE; | ||||
| 			dev_dbg(&dev->pdev->dev, | ||||
| 				"aliveness settled after %d msecs\n", t); | ||||
| 			return t; | ||||
|  | @ -223,6 +224,7 @@ static int mei_txe_aliveness_poll(struct mei_device *dev, u32 expected) | |||
| 		t += MSEC_PER_SEC / 5; | ||||
| 	} while (t < SEC_ALIVENESS_WAIT_TIMEOUT); | ||||
| 
 | ||||
| 	dev->pg_event = MEI_PG_EVENT_IDLE; | ||||
| 	dev_err(&dev->pdev->dev, "aliveness timed out\n"); | ||||
| 	return -ETIME; | ||||
| } | ||||
|  | @ -249,19 +251,22 @@ static int mei_txe_aliveness_wait(struct mei_device *dev, u32 expected) | |||
| 		return 0; | ||||
| 
 | ||||
| 	mutex_unlock(&dev->device_lock); | ||||
| 	err = wait_event_timeout(hw->wait_aliveness, | ||||
| 			hw->recvd_aliveness, timeout); | ||||
| 	err = wait_event_timeout(hw->wait_aliveness_resp, | ||||
| 			dev->pg_event == MEI_PG_EVENT_RECEIVED, timeout); | ||||
| 	mutex_lock(&dev->device_lock); | ||||
| 
 | ||||
| 	hw->aliveness = mei_txe_aliveness_get(dev); | ||||
| 	ret = hw->aliveness == expected ? 0 : -ETIME; | ||||
| 
 | ||||
| 	if (ret) | ||||
| 		dev_err(&dev->pdev->dev, "aliveness timed out"); | ||||
| 		dev_warn(&dev->pdev->dev, "aliveness timed out = %ld aliveness = %d event = %d\n", | ||||
| 			err, hw->aliveness, dev->pg_event); | ||||
| 	else | ||||
| 		dev_dbg(&dev->pdev->dev, "aliveness settled after %d msecs\n", | ||||
| 				jiffies_to_msecs(timeout - err)); | ||||
| 	hw->recvd_aliveness = false; | ||||
| 		dev_dbg(&dev->pdev->dev, "aliveness settled after = %d msec aliveness = %d event = %d\n", | ||||
| 			jiffies_to_msecs(timeout - err), | ||||
| 			hw->aliveness, dev->pg_event); | ||||
| 
 | ||||
| 	dev->pg_event = MEI_PG_EVENT_IDLE; | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
|  | @ -279,6 +284,32 @@ int mei_txe_aliveness_set_sync(struct mei_device *dev, u32 req) | |||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * mei_txe_pg_is_enabled - detect if PG is supported by HW | ||||
|  * | ||||
|  * @dev: the device structure | ||||
|  * | ||||
|  * returns: true is pg supported, false otherwise | ||||
|  */ | ||||
| static bool mei_txe_pg_is_enabled(struct mei_device *dev) | ||||
| { | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * mei_txe_pg_state  - translate aliveness register value | ||||
|  *   to the mei power gating state | ||||
|  * | ||||
|  * @dev: the device structure | ||||
|  * | ||||
|  * returns: MEI_PG_OFF if aliveness is on and MEI_PG_ON otherwise | ||||
|  */ | ||||
| static inline enum mei_pg_state mei_txe_pg_state(struct mei_device *dev) | ||||
| { | ||||
| 	struct mei_txe_hw *hw = to_txe_hw(dev); | ||||
| 	return hw->aliveness ? MEI_PG_OFF : MEI_PG_ON; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * mei_txe_input_ready_interrupt_enable - sets the Input Ready Interrupt | ||||
|  * | ||||
|  | @ -589,7 +620,10 @@ static int mei_txe_write(struct mei_device *dev, | |||
| 	mei_txe_input_ready_interrupt_enable(dev); | ||||
| 
 | ||||
| 	if (!mei_txe_is_input_ready(dev)) { | ||||
| 		dev_err(&dev->pdev->dev, "Input is not ready"); | ||||
| 		struct mei_fw_status fw_status; | ||||
| 		mei_fw_status(dev, &fw_status); | ||||
| 		dev_err(&dev->pdev->dev, "Input is not ready " FW_STS_FMT "\n", | ||||
| 			FW_STS_PRM(fw_status)); | ||||
| 		return -EAGAIN; | ||||
| 	} | ||||
| 
 | ||||
|  | @ -960,9 +994,9 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id) | |||
| 		/* Clear the interrupt cause */ | ||||
| 		dev_dbg(&dev->pdev->dev, | ||||
| 			"Aliveness Interrupt: Status: %d\n", hw->aliveness); | ||||
| 		hw->recvd_aliveness = true; | ||||
| 		if (waitqueue_active(&hw->wait_aliveness)) | ||||
| 			wake_up(&hw->wait_aliveness); | ||||
| 		dev->pg_event = MEI_PG_EVENT_RECEIVED; | ||||
| 		if (waitqueue_active(&hw->wait_aliveness_resp)) | ||||
| 			wake_up(&hw->wait_aliveness_resp); | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
|  | @ -1008,15 +1042,51 @@ end: | |||
| 	return IRQ_HANDLED; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * mei_txe_fw_status - retrieve fw status from the pci config space | ||||
|  * | ||||
|  * @dev: the device structure | ||||
|  * @fw_status: fw status registers storage | ||||
|  * | ||||
|  * returns: 0 on success an error code otherwise | ||||
|  */ | ||||
| static int mei_txe_fw_status(struct mei_device *dev, | ||||
| 			     struct mei_fw_status *fw_status) | ||||
| { | ||||
| 	const u32 pci_cfg_reg[] = {PCI_CFG_TXE_FW_STS0, PCI_CFG_TXE_FW_STS1}; | ||||
| 	int i; | ||||
| 
 | ||||
| 	if (!fw_status) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	fw_status->count = 2; | ||||
| 
 | ||||
| 	for (i = 0; i < fw_status->count && i < MEI_FW_STATUS_MAX; i++) { | ||||
| 		int ret; | ||||
| 		ret = pci_read_config_dword(dev->pdev, | ||||
| 				pci_cfg_reg[i], &fw_status->status[i]); | ||||
| 		if (ret) | ||||
| 			return ret; | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static const struct mei_hw_ops mei_txe_hw_ops = { | ||||
| 
 | ||||
| 	.fw_status = mei_txe_fw_status, | ||||
| 	.host_is_ready = mei_txe_host_is_ready, | ||||
| 
 | ||||
| 	.pg_state = mei_txe_pg_state, | ||||
| 
 | ||||
| 	.hw_is_ready = mei_txe_hw_is_ready, | ||||
| 	.hw_reset = mei_txe_hw_reset, | ||||
| 	.hw_config = mei_txe_hw_config, | ||||
| 	.hw_start = mei_txe_hw_start, | ||||
| 
 | ||||
| 	.pg_is_enabled = mei_txe_pg_is_enabled, | ||||
| 
 | ||||
| 	.intr_clear = mei_txe_intr_clear, | ||||
| 	.intr_enable = mei_txe_intr_enable, | ||||
| 	.intr_disable = mei_txe_intr_disable, | ||||
|  | @ -1034,14 +1104,27 @@ static const struct mei_hw_ops mei_txe_hw_ops = { | |||
| 
 | ||||
| }; | ||||
| 
 | ||||
| #define MEI_CFG_TXE_FW_STS                            \ | ||||
| 	.fw_status.count = 2,                         \ | ||||
| 	.fw_status.status[0] = PCI_CFG_TXE_FW_STS0,   \ | ||||
| 	.fw_status.status[1] = PCI_CFG_TXE_FW_STS1 | ||||
| 
 | ||||
| const struct mei_cfg mei_txe_cfg = { | ||||
| 	MEI_CFG_TXE_FW_STS, | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * mei_txe_dev_init - allocates and initializes txe hardware specific structure | ||||
|  * | ||||
|  * @pdev - pci device | ||||
|  * @cfg - per device generation config | ||||
|  * | ||||
|  * returns struct mei_device * on success or NULL; | ||||
|  * | ||||
|  */ | ||||
| struct mei_device *mei_txe_dev_init(struct pci_dev *pdev) | ||||
| struct mei_device *mei_txe_dev_init(struct pci_dev *pdev, | ||||
| 				    const struct mei_cfg *cfg) | ||||
| { | ||||
| 	struct mei_device *dev; | ||||
| 	struct mei_txe_hw *hw; | ||||
|  | @ -1051,11 +1134,11 @@ struct mei_device *mei_txe_dev_init(struct pci_dev *pdev) | |||
| 	if (!dev) | ||||
| 		return NULL; | ||||
| 
 | ||||
| 	mei_device_init(dev); | ||||
| 	mei_device_init(dev, cfg); | ||||
| 
 | ||||
| 	hw = to_txe_hw(dev); | ||||
| 
 | ||||
| 	init_waitqueue_head(&hw->wait_aliveness); | ||||
| 	init_waitqueue_head(&hw->wait_aliveness_resp); | ||||
| 
 | ||||
| 	dev->ops = &mei_txe_hw_ops; | ||||
| 
 | ||||
|  |  | |||
|  | @ -22,6 +22,8 @@ | |||
| #include "hw.h" | ||||
| #include "hw-txe-regs.h" | ||||
| 
 | ||||
| #define MEI_TXI_RPM_TIMEOUT    500 /* ms */ | ||||
| 
 | ||||
| /* Flatten Hierarchy interrupt cause */ | ||||
| #define TXE_INTR_READINESS_BIT  0 /* HISR_INT_0_STS */ | ||||
| #define TXE_INTR_READINESS      HISR_INT_0_STS | ||||
|  | @ -35,12 +37,11 @@ | |||
| /**
 | ||||
|  * struct mei_txe_hw - txe hardware specifics | ||||
|  * | ||||
|  * @mem_addr:        SeC and BRIDGE bars | ||||
|  * @aliveness:       aliveness (power gating) state of the hardware | ||||
|  * @readiness:       readiness state of the hardware | ||||
|  * @wait_aliveness:  aliveness wait queue | ||||
|  * @recvd_aliveness: aliveness interrupt was recived | ||||
|  * @intr_cause:      translated interrupt cause | ||||
|  * @mem_addr:            SeC and BRIDGE bars | ||||
|  * @aliveness:           aliveness (power gating) state of the hardware | ||||
|  * @readiness:           readiness state of the hardware | ||||
|  * @wait_aliveness_resp: aliveness wait queue | ||||
|  * @intr_cause:          translated interrupt cause | ||||
|  */ | ||||
| struct mei_txe_hw { | ||||
| 	void __iomem *mem_addr[NUM_OF_MEM_BARS]; | ||||
|  | @ -48,8 +49,7 @@ struct mei_txe_hw { | |||
| 	u32 readiness; | ||||
| 	u32 slots; | ||||
| 
 | ||||
| 	wait_queue_head_t wait_aliveness; | ||||
| 	bool recvd_aliveness; | ||||
| 	wait_queue_head_t wait_aliveness_resp; | ||||
| 
 | ||||
| 	unsigned long intr_cause; | ||||
| }; | ||||
|  | @ -61,7 +61,10 @@ static inline struct mei_device *hw_txe_to_mei(struct mei_txe_hw *hw) | |||
| 	return container_of((void *)hw, struct mei_device, hw); | ||||
| } | ||||
| 
 | ||||
| struct mei_device *mei_txe_dev_init(struct pci_dev *pdev); | ||||
| extern const struct mei_cfg mei_txe_cfg; | ||||
| 
 | ||||
| struct mei_device *mei_txe_dev_init(struct pci_dev *pdev, | ||||
| 	const struct mei_cfg *cfg); | ||||
| 
 | ||||
| irqreturn_t mei_txe_irq_quick_handler(int irq, void *dev_id); | ||||
| irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id); | ||||
|  |  | |||
|  | @ -31,14 +31,21 @@ | |||
| #define MEI_IAMTHIF_STALL_TIMER    12  /* HPS */ | ||||
| #define MEI_IAMTHIF_READ_TIMER     10  /* HPS */ | ||||
| 
 | ||||
| #define MEI_PGI_TIMEOUT            1  /* PG Isolation time response 1 sec */ | ||||
| #define MEI_HBM_TIMEOUT            1   /* 1 second */ | ||||
| 
 | ||||
| /*
 | ||||
|  * MEI Version | ||||
|  */ | ||||
| #define HBM_MINOR_VERSION                   0 | ||||
| #define HBM_MINOR_VERSION                   1 | ||||
| #define HBM_MAJOR_VERSION                   1 | ||||
| 
 | ||||
| /*
 | ||||
|  * MEI version with PGI support | ||||
|  */ | ||||
| #define HBM_MINOR_VERSION_PGI               1 | ||||
| #define HBM_MAJOR_VERSION_PGI               1 | ||||
| 
 | ||||
| /* Host bus message command opcode */ | ||||
| #define MEI_HBM_CMD_OP_MSK                  0x7f | ||||
| /* Host bus message command RESPONSE */ | ||||
|  | @ -69,6 +76,11 @@ | |||
| 
 | ||||
| #define MEI_FLOW_CONTROL_CMD                0x08 | ||||
| 
 | ||||
| #define MEI_PG_ISOLATION_ENTRY_REQ_CMD      0x0a | ||||
| #define MEI_PG_ISOLATION_ENTRY_RES_CMD      0x8a | ||||
| #define MEI_PG_ISOLATION_EXIT_REQ_CMD       0x0b | ||||
| #define MEI_PG_ISOLATION_EXIT_RES_CMD       0x8b | ||||
| 
 | ||||
| /*
 | ||||
|  * MEI Stop Reason | ||||
|  * used by hbm_host_stop_request.reason | ||||
|  | @ -207,6 +219,17 @@ struct hbm_props_response { | |||
| 	struct mei_client_properties client_properties; | ||||
| } __packed; | ||||
| 
 | ||||
| /**
 | ||||
|  * struct hbm_power_gate - power gate request/response | ||||
|  * | ||||
|  * @hbm_cmd - bus message command header | ||||
|  * @reserved[3] | ||||
|  */ | ||||
| struct hbm_power_gate { | ||||
| 	u8 hbm_cmd; | ||||
| 	u8 reserved[3]; | ||||
| } __packed; | ||||
| 
 | ||||
| /**
 | ||||
|  * struct hbm_client_connect_request - connect/disconnect request | ||||
|  * | ||||
|  |  | |||
|  | @ -74,9 +74,13 @@ int mei_reset(struct mei_device *dev) | |||
| 	if (state != MEI_DEV_INITIALIZING && | ||||
| 	    state != MEI_DEV_DISABLED && | ||||
| 	    state != MEI_DEV_POWER_DOWN && | ||||
| 	    state != MEI_DEV_POWER_UP) | ||||
| 		dev_warn(&dev->pdev->dev, "unexpected reset: dev_state = %s\n", | ||||
| 			 mei_dev_state_str(state)); | ||||
| 	    state != MEI_DEV_POWER_UP) { | ||||
| 		struct mei_fw_status fw_status; | ||||
| 		mei_fw_status(dev, &fw_status); | ||||
| 		dev_warn(&dev->pdev->dev, | ||||
| 			"unexpected reset: dev_state = %s " FW_STS_FMT "\n", | ||||
| 			mei_dev_state_str(state), FW_STS_PRM(fw_status)); | ||||
| 	} | ||||
| 
 | ||||
| 	/* we're already in reset, cancel the init timer
 | ||||
| 	 * if the reset was called due the hbm protocol error | ||||
|  | @ -118,8 +122,8 @@ int mei_reset(struct mei_device *dev) | |||
| 		mei_amthif_reset_params(dev); | ||||
| 	} | ||||
| 
 | ||||
| 	mei_hbm_reset(dev); | ||||
| 
 | ||||
| 	dev->me_clients_num = 0; | ||||
| 	dev->rd_msg_hdr = 0; | ||||
| 	dev->wd_pending = false; | ||||
| 
 | ||||
|  | @ -303,15 +307,58 @@ void mei_stop(struct mei_device *dev) | |||
| } | ||||
| EXPORT_SYMBOL_GPL(mei_stop); | ||||
| 
 | ||||
| /**
 | ||||
|  * mei_write_is_idle - check if the write queues are idle | ||||
|  * | ||||
|  * @dev: the device structure | ||||
|  * | ||||
|  * returns true of there is no pending write | ||||
|  */ | ||||
| bool mei_write_is_idle(struct mei_device *dev) | ||||
| { | ||||
| 	bool idle = (dev->dev_state == MEI_DEV_ENABLED && | ||||
| 		list_empty(&dev->ctrl_wr_list.list) && | ||||
| 		list_empty(&dev->write_list.list)); | ||||
| 
 | ||||
| 	dev_dbg(&dev->pdev->dev, "write pg: is idle[%d] state=%s ctrl=%d write=%d\n", | ||||
| 		idle, | ||||
| 		mei_dev_state_str(dev->dev_state), | ||||
| 		list_empty(&dev->ctrl_wr_list.list), | ||||
| 		list_empty(&dev->write_list.list)); | ||||
| 
 | ||||
| void mei_device_init(struct mei_device *dev) | ||||
| 	return idle; | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(mei_write_is_idle); | ||||
| 
 | ||||
| int mei_fw_status(struct mei_device *dev, struct mei_fw_status *fw_status) | ||||
| { | ||||
| 	int i; | ||||
| 	const struct mei_fw_status *fw_src = &dev->cfg->fw_status; | ||||
| 
 | ||||
| 	if (!fw_status) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	fw_status->count = fw_src->count; | ||||
| 	for (i = 0; i < fw_src->count && i < MEI_FW_STATUS_MAX; i++) { | ||||
| 		int ret; | ||||
| 		ret = pci_read_config_dword(dev->pdev, | ||||
| 			fw_src->status[i], &fw_status->status[i]); | ||||
| 		if (ret) | ||||
| 			return ret; | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(mei_fw_status); | ||||
| 
 | ||||
| void mei_device_init(struct mei_device *dev, const struct mei_cfg *cfg) | ||||
| { | ||||
| 	/* setup our list array */ | ||||
| 	INIT_LIST_HEAD(&dev->file_list); | ||||
| 	INIT_LIST_HEAD(&dev->device_list); | ||||
| 	mutex_init(&dev->device_lock); | ||||
| 	init_waitqueue_head(&dev->wait_hw_ready); | ||||
| 	init_waitqueue_head(&dev->wait_pg); | ||||
| 	init_waitqueue_head(&dev->wait_recvd_msg); | ||||
| 	init_waitqueue_head(&dev->wait_stop_wd); | ||||
| 	dev->dev_state = MEI_DEV_INITIALIZING; | ||||
|  | @ -340,6 +387,9 @@ void mei_device_init(struct mei_device *dev) | |||
| 	 * 0: Reserved for MEI Bus Message communications | ||||
| 	 */ | ||||
| 	bitmap_set(dev->host_clients_map, 0, 1); | ||||
| 
 | ||||
| 	dev->pg_event = MEI_PG_EVENT_IDLE; | ||||
| 	dev->cfg      = cfg; | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(mei_device_init); | ||||
| 
 | ||||
|  |  | |||
|  | @ -467,7 +467,6 @@ static int mei_ioctl_connect_client(struct file *file, | |||
| 	} | ||||
| 
 | ||||
| 	cl->me_client_id = dev->me_clients[i].client_id; | ||||
| 	cl->state = MEI_FILE_CONNECTING; | ||||
| 
 | ||||
| 	dev_dbg(&dev->pdev->dev, "Connect to FW Client ID = %d\n", | ||||
| 			cl->me_client_id); | ||||
|  |  | |||
|  | @ -153,6 +153,20 @@ struct mei_msg_data { | |||
| 	unsigned char *data; | ||||
| }; | ||||
| 
 | ||||
| /* Maximum number of processed FW status registers */ | ||||
| #define MEI_FW_STATUS_MAX 2 | ||||
| 
 | ||||
| /*
 | ||||
|  * struct mei_fw_status - storage of FW status data | ||||
|  * | ||||
|  * @count - number of actually available elements in array | ||||
|  * @status - FW status registers | ||||
|  */ | ||||
| struct mei_fw_status { | ||||
| 	int count; | ||||
| 	u32 status[MEI_FW_STATUS_MAX]; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * struct mei_me_client - representation of me (fw) client | ||||
|  * | ||||
|  | @ -213,6 +227,7 @@ struct mei_cl { | |||
| 
 | ||||
| /** struct mei_hw_ops
 | ||||
|  * | ||||
|  * @fw_status        - read FW status from PCI config space | ||||
|  * @host_is_ready    - query for host readiness | ||||
| 
 | ||||
|  * @hw_is_ready      - query if hw is ready | ||||
|  | @ -220,6 +235,9 @@ struct mei_cl { | |||
|  * @hw_start         - start hw after reset | ||||
|  * @hw_config        - configure hw | ||||
| 
 | ||||
|  * @pg_state         - power gating state of the device | ||||
|  * @pg_is_enabled    - is power gating enabled | ||||
| 
 | ||||
|  * @intr_clear       - clear pending interrupts | ||||
|  * @intr_enable      - enable interrupts | ||||
|  * @intr_disable     - disable interrupts | ||||
|  | @ -237,6 +255,8 @@ struct mei_cl { | |||
|  */ | ||||
| struct mei_hw_ops { | ||||
| 
 | ||||
| 	int (*fw_status)(struct mei_device *dev, | ||||
| 		struct mei_fw_status *fw_status); | ||||
| 	bool (*host_is_ready)(struct mei_device *dev); | ||||
| 
 | ||||
| 	bool (*hw_is_ready)(struct mei_device *dev); | ||||
|  | @ -244,6 +264,9 @@ struct mei_hw_ops { | |||
| 	int (*hw_start)(struct mei_device *dev); | ||||
| 	void (*hw_config)(struct mei_device *dev); | ||||
| 
 | ||||
| 	enum mei_pg_state (*pg_state)(struct mei_device *dev); | ||||
| 	bool (*pg_is_enabled)(struct mei_device *dev); | ||||
| 
 | ||||
| 	void (*intr_clear)(struct mei_device *dev); | ||||
| 	void (*intr_enable)(struct mei_device *dev); | ||||
| 	void (*intr_disable)(struct mei_device *dev); | ||||
|  | @ -331,16 +354,61 @@ struct mei_cl_device { | |||
| 	void *priv_data; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
|  /**
 | ||||
|  * enum mei_pg_event - power gating transition events | ||||
|  * | ||||
|  * @MEI_PG_EVENT_IDLE: the driver is not in power gating transition | ||||
|  * @MEI_PG_EVENT_WAIT: the driver is waiting for a pg event to complete | ||||
|  * @MEI_PG_EVENT_RECEIVED: the driver received pg event | ||||
|  */ | ||||
| enum mei_pg_event { | ||||
| 	MEI_PG_EVENT_IDLE, | ||||
| 	MEI_PG_EVENT_WAIT, | ||||
| 	MEI_PG_EVENT_RECEIVED, | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * enum mei_pg_state - device internal power gating state | ||||
|  * | ||||
|  * @MEI_PG_OFF: device is not power gated - it is active | ||||
|  * @MEI_PG_ON:  device is power gated - it is in lower power state | ||||
|  */ | ||||
| enum mei_pg_state { | ||||
| 	MEI_PG_OFF = 0, | ||||
| 	MEI_PG_ON =  1, | ||||
| }; | ||||
| 
 | ||||
| /*
 | ||||
|  * mei_cfg | ||||
|  * | ||||
|  * @fw_status - FW status | ||||
|  * @quirk_probe - device exclusion quirk | ||||
|  */ | ||||
| struct mei_cfg { | ||||
| 	const struct mei_fw_status fw_status; | ||||
| 	bool (*quirk_probe)(struct pci_dev *pdev); | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| #define MEI_PCI_DEVICE(dev, cfg) \ | ||||
| 	.vendor = PCI_VENDOR_ID_INTEL, .device = (dev), \ | ||||
| 	.subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, \ | ||||
| 	.driver_data = (kernel_ulong_t)&(cfg) | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * struct mei_device -  MEI private device struct | ||||
| 
 | ||||
|  * @reset_count - limits the number of consecutive resets | ||||
|  * @hbm_state - state of host bus message protocol | ||||
|  * @pg_event - power gating event | ||||
|  * @mem_addr - mem mapped base register address | ||||
| 
 | ||||
|  * @hbuf_depth - depth of hardware host/write buffer is slots | ||||
|  * @hbuf_is_ready - query if the host host/write buffer is ready | ||||
|  * @wr_msg - the buffer for hbm control messages | ||||
|  * @cfg - per device generation config and ops | ||||
|  */ | ||||
| struct mei_device { | ||||
| 	struct pci_dev *pdev;	/* pointer to pci device struct */ | ||||
|  | @ -371,6 +439,7 @@ struct mei_device { | |||
| 	 * waiting queue for receive message from FW | ||||
| 	 */ | ||||
| 	wait_queue_head_t wait_hw_ready; | ||||
| 	wait_queue_head_t wait_pg; | ||||
| 	wait_queue_head_t wait_recvd_msg; | ||||
| 	wait_queue_head_t wait_stop_wd; | ||||
| 
 | ||||
|  | @ -382,6 +451,14 @@ struct mei_device { | |||
| 	enum mei_hbm_state hbm_state; | ||||
| 	u16 init_clients_timer; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Power Gating support | ||||
| 	 */ | ||||
| 	enum mei_pg_event pg_event; | ||||
| #ifdef CONFIG_PM_RUNTIME | ||||
| 	struct dev_pm_domain pg_domain; | ||||
| #endif /* CONFIG_PM_RUNTIME */ | ||||
| 
 | ||||
| 	unsigned char rd_msg_buf[MEI_RD_MSG_BUF_SIZE];	/* control messages */ | ||||
| 	u32 rd_msg_hdr; | ||||
| 
 | ||||
|  | @ -442,6 +519,7 @@ struct mei_device { | |||
| 
 | ||||
| 
 | ||||
| 	const struct mei_hw_ops *ops; | ||||
| 	const struct mei_cfg *cfg; | ||||
| 	char hw[0] __aligned(sizeof(void *)); | ||||
| }; | ||||
| 
 | ||||
|  | @ -474,7 +552,7 @@ static inline u32 mei_slots2data(int slots) | |||
| /*
 | ||||
|  * mei init function prototypes | ||||
|  */ | ||||
| void mei_device_init(struct mei_device *dev); | ||||
| void mei_device_init(struct mei_device *dev, const struct mei_cfg *cfg); | ||||
| int mei_reset(struct mei_device *dev); | ||||
| int mei_start(struct mei_device *dev); | ||||
| int mei_restart(struct mei_device *dev); | ||||
|  | @ -553,10 +631,22 @@ void mei_watchdog_unregister(struct mei_device *dev); | |||
|  * Register Access Function | ||||
|  */ | ||||
| 
 | ||||
| 
 | ||||
| static inline void mei_hw_config(struct mei_device *dev) | ||||
| { | ||||
| 	dev->ops->hw_config(dev); | ||||
| } | ||||
| 
 | ||||
| static inline enum mei_pg_state mei_pg_state(struct mei_device *dev) | ||||
| { | ||||
| 	return dev->ops->pg_state(dev); | ||||
| } | ||||
| 
 | ||||
| static inline bool mei_pg_is_enabled(struct mei_device *dev) | ||||
| { | ||||
| 	return dev->ops->pg_is_enabled(dev); | ||||
| } | ||||
| 
 | ||||
| static inline int mei_hw_reset(struct mei_device *dev, bool enable) | ||||
| { | ||||
| 	return dev->ops->hw_reset(dev, enable); | ||||
|  | @ -629,8 +719,17 @@ static inline int mei_count_full_read_slots(struct mei_device *dev) | |||
| 	return dev->ops->rdbuf_full_slots(dev); | ||||
| } | ||||
| 
 | ||||
| int mei_fw_status(struct mei_device *dev, struct mei_fw_status *fw_status); | ||||
| 
 | ||||
| #define FW_STS_FMT "%08X %08X" | ||||
| #define FW_STS_PRM(fw_status) \ | ||||
| 	(fw_status).count > 0 ? (fw_status).status[0] : 0xDEADBEEF, \ | ||||
| 	(fw_status).count > 1 ? (fw_status).status[1] : 0xDEADBEEF | ||||
| 
 | ||||
| bool mei_hbuf_acquire(struct mei_device *dev); | ||||
| 
 | ||||
| bool mei_write_is_idle(struct mei_device *dev); | ||||
| 
 | ||||
| #if IS_ENABLED(CONFIG_DEBUG_FS) | ||||
| int mei_dbgfs_register(struct mei_device *dev, const char *name); | ||||
| void mei_dbgfs_deregister(struct mei_device *dev); | ||||
|  |  | |||
|  | @ -33,6 +33,8 @@ | |||
| #include <linux/interrupt.h> | ||||
| #include <linux/miscdevice.h> | ||||
| 
 | ||||
| #include <linux/pm_runtime.h> | ||||
| 
 | ||||
| #include <linux/mei.h> | ||||
| 
 | ||||
| #include "mei_dev.h" | ||||
|  | @ -42,42 +44,44 @@ | |||
| 
 | ||||
| /* mei_pci_tbl - PCI Device ID Table */ | ||||
| static const struct pci_device_id mei_me_pci_tbl[] = { | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82946GZ)}, | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82G35)}, | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82Q965)}, | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82G965)}, | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82GM965)}, | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82GME965)}, | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_82Q35)}, | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_82G33)}, | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_82Q33)}, | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_82X38)}, | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_3200)}, | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_6)}, | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_7)}, | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_8)}, | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_9)}, | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_10)}, | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9M_1)}, | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9M_2)}, | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9M_3)}, | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9M_4)}, | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH10_1)}, | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH10_2)}, | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH10_3)}, | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH10_4)}, | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_IBXPK_1)}, | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_IBXPK_2)}, | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_CPT_1)}, | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_PBG_1)}, | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_PPT_1)}, | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_PPT_2)}, | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_PPT_3)}, | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_LPT_H)}, | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_LPT_W)}, | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_LPT_LP)}, | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_LPT_HR)}, | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_WPT_LP)}, | ||||
| 	{MEI_PCI_DEVICE(MEI_DEV_ID_82946GZ, mei_me_legacy_cfg)}, | ||||
| 	{MEI_PCI_DEVICE(MEI_DEV_ID_82G35, mei_me_legacy_cfg)}, | ||||
| 	{MEI_PCI_DEVICE(MEI_DEV_ID_82Q965, mei_me_legacy_cfg)}, | ||||
| 	{MEI_PCI_DEVICE(MEI_DEV_ID_82G965, mei_me_legacy_cfg)}, | ||||
| 	{MEI_PCI_DEVICE(MEI_DEV_ID_82GM965, mei_me_legacy_cfg)}, | ||||
| 	{MEI_PCI_DEVICE(MEI_DEV_ID_82GME965, mei_me_legacy_cfg)}, | ||||
| 	{MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_82Q35, mei_me_legacy_cfg)}, | ||||
| 	{MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_82G33, mei_me_legacy_cfg)}, | ||||
| 	{MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_82Q33, mei_me_legacy_cfg)}, | ||||
| 	{MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_82X38, mei_me_legacy_cfg)}, | ||||
| 	{MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_3200, mei_me_legacy_cfg)}, | ||||
| 
 | ||||
| 	{MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_6, mei_me_legacy_cfg)}, | ||||
| 	{MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_7, mei_me_legacy_cfg)}, | ||||
| 	{MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_8, mei_me_legacy_cfg)}, | ||||
| 	{MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_9, mei_me_legacy_cfg)}, | ||||
| 	{MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_10, mei_me_legacy_cfg)}, | ||||
| 	{MEI_PCI_DEVICE(MEI_DEV_ID_ICH9M_1, mei_me_legacy_cfg)}, | ||||
| 	{MEI_PCI_DEVICE(MEI_DEV_ID_ICH9M_2, mei_me_legacy_cfg)}, | ||||
| 	{MEI_PCI_DEVICE(MEI_DEV_ID_ICH9M_3, mei_me_legacy_cfg)}, | ||||
| 	{MEI_PCI_DEVICE(MEI_DEV_ID_ICH9M_4, mei_me_legacy_cfg)}, | ||||
| 	{MEI_PCI_DEVICE(MEI_DEV_ID_ICH10_1, mei_me_ich_cfg)}, | ||||
| 	{MEI_PCI_DEVICE(MEI_DEV_ID_ICH10_2, mei_me_ich_cfg)}, | ||||
| 	{MEI_PCI_DEVICE(MEI_DEV_ID_ICH10_3, mei_me_ich_cfg)}, | ||||
| 	{MEI_PCI_DEVICE(MEI_DEV_ID_ICH10_4, mei_me_ich_cfg)}, | ||||
| 
 | ||||
| 	{MEI_PCI_DEVICE(MEI_DEV_ID_IBXPK_1, mei_me_pch_cfg)}, | ||||
| 	{MEI_PCI_DEVICE(MEI_DEV_ID_IBXPK_2, mei_me_pch_cfg)}, | ||||
| 	{MEI_PCI_DEVICE(MEI_DEV_ID_CPT_1, mei_me_pch_cpt_pbg_cfg)}, | ||||
| 	{MEI_PCI_DEVICE(MEI_DEV_ID_PBG_1, mei_me_pch_cpt_pbg_cfg)}, | ||||
| 	{MEI_PCI_DEVICE(MEI_DEV_ID_PPT_1, mei_me_pch_cfg)}, | ||||
| 	{MEI_PCI_DEVICE(MEI_DEV_ID_PPT_2, mei_me_pch_cfg)}, | ||||
| 	{MEI_PCI_DEVICE(MEI_DEV_ID_PPT_3, mei_me_pch_cfg)}, | ||||
| 	{MEI_PCI_DEVICE(MEI_DEV_ID_LPT_H, mei_me_lpt_cfg)}, | ||||
| 	{MEI_PCI_DEVICE(MEI_DEV_ID_LPT_W, mei_me_lpt_cfg)}, | ||||
| 	{MEI_PCI_DEVICE(MEI_DEV_ID_LPT_LP, mei_me_pch_cfg)}, | ||||
| 	{MEI_PCI_DEVICE(MEI_DEV_ID_LPT_HR, mei_me_lpt_cfg)}, | ||||
| 	{MEI_PCI_DEVICE(MEI_DEV_ID_WPT_LP, mei_me_pch_cfg)}, | ||||
| 
 | ||||
| 	/* required last entry */ | ||||
| 	{0, } | ||||
|  | @ -85,44 +89,33 @@ static const struct pci_device_id mei_me_pci_tbl[] = { | |||
| 
 | ||||
| MODULE_DEVICE_TABLE(pci, mei_me_pci_tbl); | ||||
| 
 | ||||
| #ifdef CONFIG_PM_RUNTIME | ||||
| static inline void mei_me_set_pm_domain(struct mei_device *dev); | ||||
| static inline void mei_me_unset_pm_domain(struct mei_device *dev); | ||||
| #else | ||||
| static inline void mei_me_set_pm_domain(struct mei_device *dev) {} | ||||
| static inline void mei_me_unset_pm_domain(struct mei_device *dev) {} | ||||
| #endif /* CONFIG_PM_RUNTIME */ | ||||
| 
 | ||||
| /**
 | ||||
|  * mei_quirk_probe - probe for devices that doesn't valid ME interface | ||||
|  * | ||||
|  * @pdev: PCI device structure | ||||
|  * @ent: entry into pci_device_table | ||||
|  * @cfg: per generation config | ||||
|  * | ||||
|  * returns true if ME Interface is valid, false otherwise | ||||
|  */ | ||||
| static bool mei_me_quirk_probe(struct pci_dev *pdev, | ||||
| 				const struct pci_device_id *ent) | ||||
| 				const struct mei_cfg *cfg) | ||||
| { | ||||
| 	u32 reg; | ||||
| 	/* Cougar Point || Patsburg */ | ||||
| 	if (ent->device == MEI_DEV_ID_CPT_1 || | ||||
| 	    ent->device == MEI_DEV_ID_PBG_1) { | ||||
| 		pci_read_config_dword(pdev, PCI_CFG_HFS_2, ®); | ||||
| 		/* make sure that bit 9 (NM) is up and bit 10 (DM) is down */ | ||||
| 		if ((reg & 0x600) == 0x200) | ||||
| 			goto no_mei; | ||||
| 	} | ||||
| 
 | ||||
| 	/* Lynx Point */ | ||||
| 	if (ent->device == MEI_DEV_ID_LPT_H  || | ||||
| 	    ent->device == MEI_DEV_ID_LPT_W  || | ||||
| 	    ent->device == MEI_DEV_ID_LPT_HR) { | ||||
| 		/* Read ME FW Status check for SPS Firmware */ | ||||
| 		pci_read_config_dword(pdev, PCI_CFG_HFS_1, ®); | ||||
| 		/* if bits [19:16] = 15, running SPS Firmware */ | ||||
| 		if ((reg & 0xf0000) == 0xf0000) | ||||
| 			goto no_mei; | ||||
| 	if (cfg->quirk_probe && cfg->quirk_probe(pdev)) { | ||||
| 		dev_info(&pdev->dev, "Device doesn't have valid ME Interface\n"); | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
| 	return true; | ||||
| 
 | ||||
| no_mei: | ||||
| 	dev_info(&pdev->dev, "Device doesn't have valid ME Interface\n"); | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * mei_probe - Device Initialization Routine | ||||
|  * | ||||
|  | @ -133,15 +126,14 @@ no_mei: | |||
|  */ | ||||
| static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent) | ||||
| { | ||||
| 	const struct mei_cfg *cfg = (struct mei_cfg *)(ent->driver_data); | ||||
| 	struct mei_device *dev; | ||||
| 	struct mei_me_hw *hw; | ||||
| 	int err; | ||||
| 
 | ||||
| 
 | ||||
| 	if (!mei_me_quirk_probe(pdev, ent)) { | ||||
| 		err = -ENODEV; | ||||
| 		goto end; | ||||
| 	} | ||||
| 	if (!mei_me_quirk_probe(pdev, cfg)) | ||||
| 		return -ENODEV; | ||||
| 
 | ||||
| 	/* enable pci dev */ | ||||
| 	err = pci_enable_device(pdev); | ||||
|  | @ -173,7 +165,7 @@ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent) | |||
| 
 | ||||
| 
 | ||||
| 	/* allocates and initializes the mei dev structure */ | ||||
| 	dev = mei_me_dev_init(pdev); | ||||
| 	dev = mei_me_dev_init(pdev, cfg); | ||||
| 	if (!dev) { | ||||
| 		err = -ENOMEM; | ||||
| 		goto release_regions; | ||||
|  | @ -212,6 +204,9 @@ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent) | |||
| 		goto release_irq; | ||||
| 	} | ||||
| 
 | ||||
| 	pm_runtime_set_autosuspend_delay(&pdev->dev, MEI_ME_RPM_TIMEOUT); | ||||
| 	pm_runtime_use_autosuspend(&pdev->dev); | ||||
| 
 | ||||
| 	err = mei_register(dev); | ||||
| 	if (err) | ||||
| 		goto release_irq; | ||||
|  | @ -220,6 +215,17 @@ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent) | |||
| 
 | ||||
| 	schedule_delayed_work(&dev->timer_work, HZ); | ||||
| 
 | ||||
| 	/*
 | ||||
| 	* For not wake-able HW runtime pm framework | ||||
| 	* can't be used on pci device level. | ||||
| 	* Use domain runtime pm callbacks instead. | ||||
| 	*/ | ||||
| 	if (!pci_dev_run_wake(pdev)) | ||||
| 		mei_me_set_pm_domain(dev); | ||||
| 
 | ||||
| 	if (mei_pg_is_enabled(dev)) | ||||
| 		pm_runtime_put_noidle(&pdev->dev); | ||||
| 
 | ||||
| 	dev_dbg(&pdev->dev, "initialization successful.\n"); | ||||
| 
 | ||||
| 	return 0; | ||||
|  | @ -259,12 +265,18 @@ static void mei_me_remove(struct pci_dev *pdev) | |||
| 	if (!dev) | ||||
| 		return; | ||||
| 
 | ||||
| 	if (mei_pg_is_enabled(dev)) | ||||
| 		pm_runtime_get_noresume(&pdev->dev); | ||||
| 
 | ||||
| 	hw = to_me_hw(dev); | ||||
| 
 | ||||
| 
 | ||||
| 	dev_dbg(&pdev->dev, "stop\n"); | ||||
| 	mei_stop(dev); | ||||
| 
 | ||||
| 	if (!pci_dev_run_wake(pdev)) | ||||
| 		mei_me_unset_pm_domain(dev); | ||||
| 
 | ||||
| 	/* disable interrupts */ | ||||
| 	mei_disable_interrupts(dev); | ||||
| 
 | ||||
|  | @ -343,12 +355,120 @@ static int mei_me_pci_resume(struct device *device) | |||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| #endif /* CONFIG_PM_SLEEP */ | ||||
| 
 | ||||
| #ifdef CONFIG_PM_RUNTIME | ||||
| static int mei_me_pm_runtime_idle(struct device *device) | ||||
| { | ||||
| 	struct pci_dev *pdev = to_pci_dev(device); | ||||
| 	struct mei_device *dev; | ||||
| 
 | ||||
| 	dev_dbg(&pdev->dev, "rpm: me: runtime_idle\n"); | ||||
| 
 | ||||
| 	dev = pci_get_drvdata(pdev); | ||||
| 	if (!dev) | ||||
| 		return -ENODEV; | ||||
| 	if (mei_write_is_idle(dev)) | ||||
| 		pm_schedule_suspend(device, MEI_ME_RPM_TIMEOUT * 2); | ||||
| 
 | ||||
| 	return -EBUSY; | ||||
| } | ||||
| 
 | ||||
| static int mei_me_pm_runtime_suspend(struct device *device) | ||||
| { | ||||
| 	struct pci_dev *pdev = to_pci_dev(device); | ||||
| 	struct mei_device *dev; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	dev_dbg(&pdev->dev, "rpm: me: runtime suspend\n"); | ||||
| 
 | ||||
| 	dev = pci_get_drvdata(pdev); | ||||
| 	if (!dev) | ||||
| 		return -ENODEV; | ||||
| 
 | ||||
| 	mutex_lock(&dev->device_lock); | ||||
| 
 | ||||
| 	if (mei_write_is_idle(dev)) | ||||
| 		ret = mei_me_pg_set_sync(dev); | ||||
| 	else | ||||
| 		ret = -EAGAIN; | ||||
| 
 | ||||
| 	mutex_unlock(&dev->device_lock); | ||||
| 
 | ||||
| 	dev_dbg(&pdev->dev, "rpm: me: runtime suspend ret=%d\n", ret); | ||||
| 
 | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| static int mei_me_pm_runtime_resume(struct device *device) | ||||
| { | ||||
| 	struct pci_dev *pdev = to_pci_dev(device); | ||||
| 	struct mei_device *dev; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	dev_dbg(&pdev->dev, "rpm: me: runtime resume\n"); | ||||
| 
 | ||||
| 	dev = pci_get_drvdata(pdev); | ||||
| 	if (!dev) | ||||
| 		return -ENODEV; | ||||
| 
 | ||||
| 	mutex_lock(&dev->device_lock); | ||||
| 
 | ||||
| 	ret = mei_me_pg_unset_sync(dev); | ||||
| 
 | ||||
| 	mutex_unlock(&dev->device_lock); | ||||
| 
 | ||||
| 	dev_dbg(&pdev->dev, "rpm: me: runtime resume ret = %d\n", ret); | ||||
| 
 | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * mei_me_set_pm_domain - fill and set pm domian stucture for device | ||||
|  * | ||||
|  * @dev: mei_device | ||||
|  */ | ||||
| static inline void mei_me_set_pm_domain(struct mei_device *dev) | ||||
| { | ||||
| 	struct pci_dev *pdev  = dev->pdev; | ||||
| 
 | ||||
| 	if (pdev->dev.bus && pdev->dev.bus->pm) { | ||||
| 		dev->pg_domain.ops = *pdev->dev.bus->pm; | ||||
| 
 | ||||
| 		dev->pg_domain.ops.runtime_suspend = mei_me_pm_runtime_suspend; | ||||
| 		dev->pg_domain.ops.runtime_resume = mei_me_pm_runtime_resume; | ||||
| 		dev->pg_domain.ops.runtime_idle = mei_me_pm_runtime_idle; | ||||
| 
 | ||||
| 		pdev->dev.pm_domain = &dev->pg_domain; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * mei_me_unset_pm_domain - clean pm domian stucture for device | ||||
|  * | ||||
|  * @dev: mei_device | ||||
|  */ | ||||
| static inline void mei_me_unset_pm_domain(struct mei_device *dev) | ||||
| { | ||||
| 	/* stop using pm callbacks if any */ | ||||
| 	dev->pdev->dev.pm_domain = NULL; | ||||
| } | ||||
| #endif /* CONFIG_PM_RUNTIME */ | ||||
| 
 | ||||
| #ifdef CONFIG_PM | ||||
| static const struct dev_pm_ops mei_me_pm_ops = { | ||||
| 	SET_SYSTEM_SLEEP_PM_OPS(mei_me_pci_suspend, | ||||
| 				mei_me_pci_resume) | ||||
| 	SET_RUNTIME_PM_OPS( | ||||
| 		mei_me_pm_runtime_suspend, | ||||
| 		mei_me_pm_runtime_resume, | ||||
| 		mei_me_pm_runtime_idle) | ||||
| }; | ||||
| 
 | ||||
| static SIMPLE_DEV_PM_OPS(mei_me_pm_ops, mei_me_pci_suspend, mei_me_pci_resume); | ||||
| #define MEI_ME_PM_OPS	(&mei_me_pm_ops) | ||||
| #else | ||||
| #define MEI_ME_PM_OPS	NULL | ||||
| #endif /* CONFIG_PM_SLEEP */ | ||||
| #endif /* CONFIG_PM */ | ||||
| /*
 | ||||
|  *  PCI driver structure | ||||
|  */ | ||||
|  |  | |||
|  | @ -27,6 +27,7 @@ | |||
| #include <linux/jiffies.h> | ||||
| #include <linux/interrupt.h> | ||||
| #include <linux/workqueue.h> | ||||
| #include <linux/pm_runtime.h> | ||||
| 
 | ||||
| #include <linux/mei.h> | ||||
| 
 | ||||
|  | @ -35,11 +36,18 @@ | |||
| #include "hw-txe.h" | ||||
| 
 | ||||
| static const struct pci_device_id mei_txe_pci_tbl[] = { | ||||
| 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x0F18)}, /* Baytrail */ | ||||
| 	{MEI_PCI_DEVICE(0x0F18, mei_txe_cfg)}, /* Baytrail */ | ||||
| 	{0, } | ||||
| }; | ||||
| MODULE_DEVICE_TABLE(pci, mei_txe_pci_tbl); | ||||
| 
 | ||||
| #ifdef CONFIG_PM_RUNTIME | ||||
| static inline void mei_txe_set_pm_domain(struct mei_device *dev); | ||||
| static inline void mei_txe_unset_pm_domain(struct mei_device *dev); | ||||
| #else | ||||
| static inline void mei_txe_set_pm_domain(struct mei_device *dev) {} | ||||
| static inline void mei_txe_unset_pm_domain(struct mei_device *dev) {} | ||||
| #endif /* CONFIG_PM_RUNTIME */ | ||||
| 
 | ||||
| static void mei_txe_pci_iounmap(struct pci_dev *pdev, struct mei_txe_hw *hw) | ||||
| { | ||||
|  | @ -61,6 +69,7 @@ static void mei_txe_pci_iounmap(struct pci_dev *pdev, struct mei_txe_hw *hw) | |||
|  */ | ||||
| static int mei_txe_probe(struct pci_dev *pdev, const struct pci_device_id *ent) | ||||
| { | ||||
| 	const struct mei_cfg *cfg = (struct mei_cfg *)(ent->driver_data); | ||||
| 	struct mei_device *dev; | ||||
| 	struct mei_txe_hw *hw; | ||||
| 	int err; | ||||
|  | @ -91,7 +100,7 @@ static int mei_txe_probe(struct pci_dev *pdev, const struct pci_device_id *ent) | |||
| 	} | ||||
| 
 | ||||
| 	/* allocates and initializes the mei dev structure */ | ||||
| 	dev = mei_txe_dev_init(pdev); | ||||
| 	dev = mei_txe_dev_init(pdev, cfg); | ||||
| 	if (!dev) { | ||||
| 		err = -ENOMEM; | ||||
| 		goto release_regions; | ||||
|  | @ -137,12 +146,25 @@ static int mei_txe_probe(struct pci_dev *pdev, const struct pci_device_id *ent) | |||
| 		goto release_irq; | ||||
| 	} | ||||
| 
 | ||||
| 	pm_runtime_set_autosuspend_delay(&pdev->dev, MEI_TXI_RPM_TIMEOUT); | ||||
| 	pm_runtime_use_autosuspend(&pdev->dev); | ||||
| 
 | ||||
| 	err = mei_register(dev); | ||||
| 	if (err) | ||||
| 		goto release_irq; | ||||
| 
 | ||||
| 	pci_set_drvdata(pdev, dev); | ||||
| 
 | ||||
| 	/*
 | ||||
| 	* For not wake-able HW runtime pm framework | ||||
| 	* can't be used on pci device level. | ||||
| 	* Use domain runtime pm callbacks instead. | ||||
| 	*/ | ||||
| 	if (!pci_dev_run_wake(pdev)) | ||||
| 		mei_txe_set_pm_domain(dev); | ||||
| 
 | ||||
| 	pm_runtime_put_noidle(&pdev->dev); | ||||
| 
 | ||||
| 	return 0; | ||||
| 
 | ||||
| release_irq: | ||||
|  | @ -187,10 +209,15 @@ static void mei_txe_remove(struct pci_dev *pdev) | |||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	pm_runtime_get_noresume(&pdev->dev); | ||||
| 
 | ||||
| 	hw = to_txe_hw(dev); | ||||
| 
 | ||||
| 	mei_stop(dev); | ||||
| 
 | ||||
| 	if (!pci_dev_run_wake(pdev)) | ||||
| 		mei_txe_unset_pm_domain(dev); | ||||
| 
 | ||||
| 	/* disable interrupts */ | ||||
| 	mei_disable_interrupts(dev); | ||||
| 	free_irq(pdev->irq, dev); | ||||
|  | @ -265,15 +292,131 @@ static int mei_txe_pci_resume(struct device *device) | |||
| 
 | ||||
| 	return err; | ||||
| } | ||||
| #endif /* CONFIG_PM_SLEEP */ | ||||
| 
 | ||||
| static SIMPLE_DEV_PM_OPS(mei_txe_pm_ops, | ||||
| 			 mei_txe_pci_suspend, | ||||
| 			 mei_txe_pci_resume); | ||||
| #ifdef CONFIG_PM_RUNTIME | ||||
| static int mei_txe_pm_runtime_idle(struct device *device) | ||||
| { | ||||
| 	struct pci_dev *pdev = to_pci_dev(device); | ||||
| 	struct mei_device *dev; | ||||
| 
 | ||||
| 	dev_dbg(&pdev->dev, "rpm: txe: runtime_idle\n"); | ||||
| 
 | ||||
| 	dev = pci_get_drvdata(pdev); | ||||
| 	if (!dev) | ||||
| 		return -ENODEV; | ||||
| 	if (mei_write_is_idle(dev)) | ||||
| 		pm_schedule_suspend(device, MEI_TXI_RPM_TIMEOUT * 2); | ||||
| 
 | ||||
| 	return -EBUSY; | ||||
| } | ||||
| static int mei_txe_pm_runtime_suspend(struct device *device) | ||||
| { | ||||
| 	struct pci_dev *pdev = to_pci_dev(device); | ||||
| 	struct mei_device *dev; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	dev_dbg(&pdev->dev, "rpm: txe: runtime suspend\n"); | ||||
| 
 | ||||
| 	dev = pci_get_drvdata(pdev); | ||||
| 	if (!dev) | ||||
| 		return -ENODEV; | ||||
| 
 | ||||
| 	mutex_lock(&dev->device_lock); | ||||
| 
 | ||||
| 	if (mei_write_is_idle(dev)) | ||||
| 		ret = mei_txe_aliveness_set_sync(dev, 0); | ||||
| 	else | ||||
| 		ret = -EAGAIN; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * If everything is okay we're about to enter PCI low | ||||
| 	 * power state (D3) therefor we need to disable the | ||||
| 	 * interrupts towards host. | ||||
| 	 * However if device is not wakeable we do not enter | ||||
| 	 * D-low state and we need to keep the interrupt kicking | ||||
| 	 */ | ||||
| 	 if (!ret && pci_dev_run_wake(pdev)) | ||||
| 		mei_disable_interrupts(dev); | ||||
| 
 | ||||
| 	dev_dbg(&pdev->dev, "rpm: txe: runtime suspend ret=%d\n", ret); | ||||
| 
 | ||||
| 	mutex_unlock(&dev->device_lock); | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| static int mei_txe_pm_runtime_resume(struct device *device) | ||||
| { | ||||
| 	struct pci_dev *pdev = to_pci_dev(device); | ||||
| 	struct mei_device *dev; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	dev_dbg(&pdev->dev, "rpm: txe: runtime resume\n"); | ||||
| 
 | ||||
| 	dev = pci_get_drvdata(pdev); | ||||
| 	if (!dev) | ||||
| 		return -ENODEV; | ||||
| 
 | ||||
| 	mutex_lock(&dev->device_lock); | ||||
| 
 | ||||
| 	mei_enable_interrupts(dev); | ||||
| 
 | ||||
| 	ret = mei_txe_aliveness_set_sync(dev, 1); | ||||
| 
 | ||||
| 	mutex_unlock(&dev->device_lock); | ||||
| 
 | ||||
| 	dev_dbg(&pdev->dev, "rpm: txe: runtime resume ret = %d\n", ret); | ||||
| 
 | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * mei_txe_set_pm_domain - fill and set pm domian stucture for device | ||||
|  * | ||||
|  * @dev: mei_device | ||||
|  */ | ||||
| static inline void mei_txe_set_pm_domain(struct mei_device *dev) | ||||
| { | ||||
| 	struct pci_dev *pdev  = dev->pdev; | ||||
| 
 | ||||
| 	if (pdev->dev.bus && pdev->dev.bus->pm) { | ||||
| 		dev->pg_domain.ops = *pdev->dev.bus->pm; | ||||
| 
 | ||||
| 		dev->pg_domain.ops.runtime_suspend = mei_txe_pm_runtime_suspend; | ||||
| 		dev->pg_domain.ops.runtime_resume = mei_txe_pm_runtime_resume; | ||||
| 		dev->pg_domain.ops.runtime_idle = mei_txe_pm_runtime_idle; | ||||
| 
 | ||||
| 		pdev->dev.pm_domain = &dev->pg_domain; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * mei_txe_unset_pm_domain - clean pm domian stucture for device | ||||
|  * | ||||
|  * @dev: mei_device | ||||
|  */ | ||||
| static inline void mei_txe_unset_pm_domain(struct mei_device *dev) | ||||
| { | ||||
| 	/* stop using pm callbacks if any */ | ||||
| 	dev->pdev->dev.pm_domain = NULL; | ||||
| } | ||||
| #endif /* CONFIG_PM_RUNTIME */ | ||||
| 
 | ||||
| #ifdef CONFIG_PM | ||||
| static const struct dev_pm_ops mei_txe_pm_ops = { | ||||
| 	SET_SYSTEM_SLEEP_PM_OPS(mei_txe_pci_suspend, | ||||
| 				mei_txe_pci_resume) | ||||
| 	SET_RUNTIME_PM_OPS( | ||||
| 		mei_txe_pm_runtime_suspend, | ||||
| 		mei_txe_pm_runtime_resume, | ||||
| 		mei_txe_pm_runtime_idle) | ||||
| }; | ||||
| 
 | ||||
| #define MEI_TXE_PM_OPS	(&mei_txe_pm_ops) | ||||
| #else | ||||
| #define MEI_TXE_PM_OPS	NULL | ||||
| #endif /* CONFIG_PM_SLEEP */ | ||||
| #endif /* CONFIG_PM */ | ||||
| 
 | ||||
| /*
 | ||||
|  *  PCI driver structure | ||||
|  */ | ||||
|  |  | |||
|  | @ -84,8 +84,6 @@ int mei_wd_host_init(struct mei_device *dev) | |||
| 		return ret; | ||||
| 	} | ||||
| 
 | ||||
| 	cl->state = MEI_FILE_CONNECTING; | ||||
| 
 | ||||
| 	ret = mei_cl_connect(cl, NULL); | ||||
| 
 | ||||
| 	if (ret) { | ||||
|  |  | |||
|  | @ -266,11 +266,12 @@ config REGULATOR_LP8788 | |||
| 	  This driver supports LP8788 voltage regulator chip. | ||||
| 
 | ||||
| config REGULATOR_MAX14577 | ||||
| 	tristate "Maxim 14577 regulator" | ||||
| 	tristate "Maxim 14577/77836 regulator" | ||||
| 	depends on MFD_MAX14577 | ||||
| 	help | ||||
| 	  This driver controls a Maxim 14577 regulator via I2C bus. | ||||
| 	  The regulators include safeout LDO and current regulator 'CHARGER'. | ||||
| 	  This driver controls a Maxim MAX14577/77836 regulator via I2C bus. | ||||
| 	  The MAX14577 regulators include safeout LDO and charger current | ||||
| 	  regulator. The MAX77836 has two additional LDOs. | ||||
| 
 | ||||
| config REGULATOR_MAX1586 | ||||
| 	tristate "Maxim 1586/1587 voltage regulator" | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| /*
 | ||||
|  * max14577.c - Regulator driver for the Maxim 14577 | ||||
|  * max14577.c - Regulator driver for the Maxim 14577/77836 | ||||
|  * | ||||
|  * Copyright (C) 2013,2014 Samsung Electronics | ||||
|  * Krzysztof Kozlowski <k.kozlowski@samsung.com> | ||||
|  | @ -22,6 +22,42 @@ | |||
| #include <linux/mfd/max14577-private.h> | ||||
| #include <linux/regulator/of_regulator.h> | ||||
| 
 | ||||
| /*
 | ||||
|  * Valid limits of current for max14577 and max77836 chargers. | ||||
|  * They must correspond to MBCICHWRCL and MBCICHWRCH fields in CHGCTRL4 | ||||
|  * register for given chipset. | ||||
|  */ | ||||
| struct maxim_charger_current { | ||||
| 	/* Minimal current, set in CHGCTRL4/MBCICHWRCL, uA */ | ||||
| 	unsigned int min; | ||||
| 	/*
 | ||||
| 	 * Minimal current when high setting is active, | ||||
| 	 * set in CHGCTRL4/MBCICHWRCH, uA | ||||
| 	 */ | ||||
| 	unsigned int high_start; | ||||
| 	/* Value of one step in high setting, uA */ | ||||
| 	unsigned int high_step; | ||||
| 	/* Maximum current of high setting, uA */ | ||||
| 	unsigned int max; | ||||
| }; | ||||
| 
 | ||||
| /* Table of valid charger currents for different Maxim chipsets */ | ||||
| static const struct maxim_charger_current maxim_charger_currents[] = { | ||||
| 	[MAXIM_DEVICE_TYPE_UNKNOWN] = { 0, 0, 0, 0 }, | ||||
| 	[MAXIM_DEVICE_TYPE_MAX14577] = { | ||||
| 		.min		= MAX14577_REGULATOR_CURRENT_LIMIT_MIN, | ||||
| 		.high_start	= MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START, | ||||
| 		.high_step	= MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_STEP, | ||||
| 		.max		= MAX14577_REGULATOR_CURRENT_LIMIT_MAX, | ||||
| 	}, | ||||
| 	[MAXIM_DEVICE_TYPE_MAX77836] = { | ||||
| 		.min		= MAX77836_REGULATOR_CURRENT_LIMIT_MIN, | ||||
| 		.high_start	= MAX77836_REGULATOR_CURRENT_LIMIT_HIGH_START, | ||||
| 		.high_step	= MAX77836_REGULATOR_CURRENT_LIMIT_HIGH_STEP, | ||||
| 		.max		= MAX77836_REGULATOR_CURRENT_LIMIT_MAX, | ||||
| 	}, | ||||
| }; | ||||
| 
 | ||||
| static int max14577_reg_is_enabled(struct regulator_dev *rdev) | ||||
| { | ||||
| 	int rid = rdev_get_id(rdev); | ||||
|  | @ -47,6 +83,9 @@ static int max14577_reg_get_current_limit(struct regulator_dev *rdev) | |||
| { | ||||
| 	u8 reg_data; | ||||
| 	struct regmap *rmap = rdev->regmap; | ||||
| 	struct max14577 *max14577 = rdev_get_drvdata(rdev); | ||||
| 	const struct maxim_charger_current *limits = | ||||
| 		&maxim_charger_currents[max14577->dev_type]; | ||||
| 
 | ||||
| 	if (rdev_get_id(rdev) != MAX14577_CHARGER) | ||||
| 		return -EINVAL; | ||||
|  | @ -54,12 +93,11 @@ static int max14577_reg_get_current_limit(struct regulator_dev *rdev) | |||
| 	max14577_read_reg(rmap, MAX14577_CHG_REG_CHG_CTRL4, ®_data); | ||||
| 
 | ||||
| 	if ((reg_data & CHGCTRL4_MBCICHWRCL_MASK) == 0) | ||||
| 		return MAX14577_REGULATOR_CURRENT_LIMIT_MIN; | ||||
| 		return limits->min; | ||||
| 
 | ||||
| 	reg_data = ((reg_data & CHGCTRL4_MBCICHWRCH_MASK) >> | ||||
| 			CHGCTRL4_MBCICHWRCH_SHIFT); | ||||
| 	return MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START + | ||||
| 		reg_data * MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_STEP; | ||||
| 	return limits->high_start + reg_data * limits->high_step; | ||||
| } | ||||
| 
 | ||||
| static int max14577_reg_set_current_limit(struct regulator_dev *rdev, | ||||
|  | @ -67,33 +105,39 @@ static int max14577_reg_set_current_limit(struct regulator_dev *rdev, | |||
| { | ||||
| 	int i, current_bits = 0xf; | ||||
| 	u8 reg_data; | ||||
| 	struct max14577 *max14577 = rdev_get_drvdata(rdev); | ||||
| 	const struct maxim_charger_current *limits = | ||||
| 		&maxim_charger_currents[max14577->dev_type]; | ||||
| 
 | ||||
| 	if (rdev_get_id(rdev) != MAX14577_CHARGER) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	if (min_uA > MAX14577_REGULATOR_CURRENT_LIMIT_MAX || | ||||
| 			max_uA < MAX14577_REGULATOR_CURRENT_LIMIT_MIN) | ||||
| 	if (min_uA > limits->max || max_uA < limits->min) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	if (max_uA < MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START) { | ||||
| 		/* Less than 200 mA, so set 90mA (turn only Low Bit off) */ | ||||
| 	if (max_uA < limits->high_start) { | ||||
| 		/*
 | ||||
| 		 * Less than high_start, | ||||
| 		 * so set the minimal current (turn only Low Bit off) | ||||
| 		 */ | ||||
| 		u8 reg_data = 0x0 << CHGCTRL4_MBCICHWRCL_SHIFT; | ||||
| 		return max14577_update_reg(rdev->regmap, | ||||
| 				MAX14577_CHG_REG_CHG_CTRL4, | ||||
| 				CHGCTRL4_MBCICHWRCL_MASK, reg_data); | ||||
| 	} | ||||
| 
 | ||||
| 	/* max_uA is in range: <LIMIT_HIGH_START, inifinite>, so search for
 | ||||
| 	 * valid current starting from LIMIT_MAX. */ | ||||
| 	for (i = MAX14577_REGULATOR_CURRENT_LIMIT_MAX; | ||||
| 			i >= MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START; | ||||
| 			i -= MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_STEP) { | ||||
| 	/*
 | ||||
| 	 * max_uA is in range: <high_start, inifinite>, so search for | ||||
| 	 * valid current starting from maximum current. | ||||
| 	 */ | ||||
| 	for (i = limits->max; i >= limits->high_start; i -= limits->high_step) { | ||||
| 		if (i <= max_uA) | ||||
| 			break; | ||||
| 		current_bits--; | ||||
| 	} | ||||
| 	BUG_ON(current_bits < 0); /* Cannot happen */ | ||||
| 	/* Turn Low Bit on (use range 200mA-950 mA) */ | ||||
| 
 | ||||
| 	/* Turn Low Bit on (use range high_start-max)... */ | ||||
| 	reg_data = 0x1 << CHGCTRL4_MBCICHWRCL_SHIFT; | ||||
| 	/* and set proper High Bits */ | ||||
| 	reg_data |= current_bits << CHGCTRL4_MBCICHWRCH_SHIFT; | ||||
|  | @ -118,7 +162,7 @@ static struct regulator_ops max14577_charger_ops = { | |||
| 	.set_current_limit	= max14577_reg_set_current_limit, | ||||
| }; | ||||
| 
 | ||||
| static const struct regulator_desc supported_regulators[] = { | ||||
| static const struct regulator_desc max14577_supported_regulators[] = { | ||||
| 	[MAX14577_SAFEOUT] = { | ||||
| 		.name		= "SAFEOUT", | ||||
| 		.id		= MAX14577_SAFEOUT, | ||||
|  | @ -141,16 +185,88 @@ static const struct regulator_desc supported_regulators[] = { | |||
| 	}, | ||||
| }; | ||||
| 
 | ||||
| static struct regulator_ops max77836_ldo_ops = { | ||||
| 	.is_enabled		= regulator_is_enabled_regmap, | ||||
| 	.enable			= regulator_enable_regmap, | ||||
| 	.disable		= regulator_disable_regmap, | ||||
| 	.list_voltage		= regulator_list_voltage_linear, | ||||
| 	.map_voltage		= regulator_map_voltage_linear, | ||||
| 	.get_voltage_sel	= regulator_get_voltage_sel_regmap, | ||||
| 	.set_voltage_sel	= regulator_set_voltage_sel_regmap, | ||||
| 	/* TODO: add .set_suspend_mode */ | ||||
| }; | ||||
| 
 | ||||
| static const struct regulator_desc max77836_supported_regulators[] = { | ||||
| 	[MAX14577_SAFEOUT] = { | ||||
| 		.name		= "SAFEOUT", | ||||
| 		.id		= MAX14577_SAFEOUT, | ||||
| 		.ops		= &max14577_safeout_ops, | ||||
| 		.type		= REGULATOR_VOLTAGE, | ||||
| 		.owner		= THIS_MODULE, | ||||
| 		.n_voltages	= 1, | ||||
| 		.min_uV		= MAX14577_REGULATOR_SAFEOUT_VOLTAGE, | ||||
| 		.enable_reg	= MAX14577_REG_CONTROL2, | ||||
| 		.enable_mask	= CTRL2_SFOUTORD_MASK, | ||||
| 	}, | ||||
| 	[MAX14577_CHARGER] = { | ||||
| 		.name		= "CHARGER", | ||||
| 		.id		= MAX14577_CHARGER, | ||||
| 		.ops		= &max14577_charger_ops, | ||||
| 		.type		= REGULATOR_CURRENT, | ||||
| 		.owner		= THIS_MODULE, | ||||
| 		.enable_reg	= MAX14577_CHG_REG_CHG_CTRL2, | ||||
| 		.enable_mask	= CHGCTRL2_MBCHOSTEN_MASK, | ||||
| 	}, | ||||
| 	[MAX77836_LDO1] = { | ||||
| 		.name		= "LDO1", | ||||
| 		.id		= MAX77836_LDO1, | ||||
| 		.ops		= &max77836_ldo_ops, | ||||
| 		.type		= REGULATOR_VOLTAGE, | ||||
| 		.owner		= THIS_MODULE, | ||||
| 		.n_voltages	= MAX77836_REGULATOR_LDO_VOLTAGE_STEPS_NUM, | ||||
| 		.min_uV		= MAX77836_REGULATOR_LDO_VOLTAGE_MIN, | ||||
| 		.uV_step	= MAX77836_REGULATOR_LDO_VOLTAGE_STEP, | ||||
| 		.enable_reg	= MAX77836_LDO_REG_CNFG1_LDO1, | ||||
| 		.enable_mask	= MAX77836_CNFG1_LDO_PWRMD_MASK, | ||||
| 		.vsel_reg	= MAX77836_LDO_REG_CNFG1_LDO1, | ||||
| 		.vsel_mask	= MAX77836_CNFG1_LDO_TV_MASK, | ||||
| 	}, | ||||
| 	[MAX77836_LDO2] = { | ||||
| 		.name		= "LDO2", | ||||
| 		.id		= MAX77836_LDO2, | ||||
| 		.ops		= &max77836_ldo_ops, | ||||
| 		.type		= REGULATOR_VOLTAGE, | ||||
| 		.owner		= THIS_MODULE, | ||||
| 		.n_voltages	= MAX77836_REGULATOR_LDO_VOLTAGE_STEPS_NUM, | ||||
| 		.min_uV		= MAX77836_REGULATOR_LDO_VOLTAGE_MIN, | ||||
| 		.uV_step	= MAX77836_REGULATOR_LDO_VOLTAGE_STEP, | ||||
| 		.enable_reg	= MAX77836_LDO_REG_CNFG1_LDO2, | ||||
| 		.enable_mask	= MAX77836_CNFG1_LDO_PWRMD_MASK, | ||||
| 		.vsel_reg	= MAX77836_LDO_REG_CNFG1_LDO2, | ||||
| 		.vsel_mask	= MAX77836_CNFG1_LDO_TV_MASK, | ||||
| 	}, | ||||
| }; | ||||
| 
 | ||||
| #ifdef CONFIG_OF | ||||
| static struct of_regulator_match max14577_regulator_matches[] = { | ||||
| 	{ .name	= "SAFEOUT", }, | ||||
| 	{ .name = "CHARGER", }, | ||||
| }; | ||||
| 
 | ||||
| static int max14577_regulator_dt_parse_pdata(struct platform_device *pdev) | ||||
| static struct of_regulator_match max77836_regulator_matches[] = { | ||||
| 	{ .name	= "SAFEOUT", }, | ||||
| 	{ .name = "CHARGER", }, | ||||
| 	{ .name = "LDO1", }, | ||||
| 	{ .name = "LDO2", }, | ||||
| }; | ||||
| 
 | ||||
| static int max14577_regulator_dt_parse_pdata(struct platform_device *pdev, | ||||
| 		enum maxim_device_type dev_type) | ||||
| { | ||||
| 	int ret; | ||||
| 	struct device_node *np; | ||||
| 	struct of_regulator_match *regulator_matches; | ||||
| 	unsigned int regulator_matches_size; | ||||
| 
 | ||||
| 	np = of_get_child_by_name(pdev->dev.parent->of_node, "regulators"); | ||||
| 	if (!np) { | ||||
|  | @ -158,8 +274,19 @@ static int max14577_regulator_dt_parse_pdata(struct platform_device *pdev) | |||
| 		return -EINVAL; | ||||
| 	} | ||||
| 
 | ||||
| 	ret = of_regulator_match(&pdev->dev, np, max14577_regulator_matches, | ||||
| 			MAX14577_REG_MAX); | ||||
| 	switch (dev_type) { | ||||
| 	case MAXIM_DEVICE_TYPE_MAX77836: | ||||
| 		regulator_matches = max77836_regulator_matches; | ||||
| 		regulator_matches_size = ARRAY_SIZE(max77836_regulator_matches); | ||||
| 		break; | ||||
| 	case MAXIM_DEVICE_TYPE_MAX14577: | ||||
| 	default: | ||||
| 		regulator_matches = max14577_regulator_matches; | ||||
| 		regulator_matches_size = ARRAY_SIZE(max14577_regulator_matches); | ||||
| 	} | ||||
| 
 | ||||
| 	ret = of_regulator_match(&pdev->dev, np, regulator_matches, | ||||
| 			regulator_matches_size); | ||||
| 	if (ret < 0) | ||||
| 		dev_err(&pdev->dev, "Error parsing regulator init data: %d\n", ret); | ||||
| 	else | ||||
|  | @ -170,31 +297,74 @@ static int max14577_regulator_dt_parse_pdata(struct platform_device *pdev) | |||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| static inline struct regulator_init_data *match_init_data(int index) | ||||
| static inline struct regulator_init_data *match_init_data(int index, | ||||
| 		enum maxim_device_type dev_type) | ||||
| { | ||||
| 	return max14577_regulator_matches[index].init_data; | ||||
| 	switch (dev_type) { | ||||
| 	case MAXIM_DEVICE_TYPE_MAX77836: | ||||
| 		return max77836_regulator_matches[index].init_data; | ||||
| 
 | ||||
| 	case MAXIM_DEVICE_TYPE_MAX14577: | ||||
| 	default: | ||||
| 		return max14577_regulator_matches[index].init_data; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static inline struct device_node *match_of_node(int index) | ||||
| static inline struct device_node *match_of_node(int index, | ||||
| 		enum maxim_device_type dev_type) | ||||
| { | ||||
| 	return max14577_regulator_matches[index].of_node; | ||||
| 	switch (dev_type) { | ||||
| 	case MAXIM_DEVICE_TYPE_MAX77836: | ||||
| 		return max77836_regulator_matches[index].of_node; | ||||
| 
 | ||||
| 	case MAXIM_DEVICE_TYPE_MAX14577: | ||||
| 	default: | ||||
| 		return max14577_regulator_matches[index].of_node; | ||||
| 	} | ||||
| } | ||||
| #else /* CONFIG_OF */ | ||||
| static int max14577_regulator_dt_parse_pdata(struct platform_device *pdev) | ||||
| static int max14577_regulator_dt_parse_pdata(struct platform_device *pdev, | ||||
| 		enum maxim_device_type dev_type) | ||||
| { | ||||
| 	return 0; | ||||
| } | ||||
| static inline struct regulator_init_data *match_init_data(int index) | ||||
| static inline struct regulator_init_data *match_init_data(int index, | ||||
| 		enum maxim_device_type dev_type) | ||||
| { | ||||
| 	return NULL; | ||||
| } | ||||
| 
 | ||||
| static inline struct device_node *match_of_node(int index) | ||||
| static inline struct device_node *match_of_node(int index, | ||||
| 		enum maxim_device_type dev_type) | ||||
| { | ||||
| 	return NULL; | ||||
| } | ||||
| #endif /* CONFIG_OF */ | ||||
| 
 | ||||
| /**
 | ||||
|  * Registers for regulators of max77836 use different I2C slave addresses so | ||||
|  * different regmaps must be used for them. | ||||
|  * | ||||
|  * Returns proper regmap for accessing regulator passed by id. | ||||
|  */ | ||||
| static struct regmap *max14577_get_regmap(struct max14577 *max14577, | ||||
| 		int reg_id) | ||||
| { | ||||
| 	switch (max14577->dev_type) { | ||||
| 	case MAXIM_DEVICE_TYPE_MAX77836: | ||||
| 		switch (reg_id) { | ||||
| 		case MAX77836_SAFEOUT ... MAX77836_CHARGER: | ||||
| 			return max14577->regmap; | ||||
| 		default: | ||||
| 			/* MAX77836_LDO1 ... MAX77836_LDO2 */ | ||||
| 			return max14577->regmap_pmic; | ||||
| 		} | ||||
| 
 | ||||
| 	case MAXIM_DEVICE_TYPE_MAX14577: | ||||
| 	default: | ||||
| 		return max14577->regmap; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static int max14577_regulator_probe(struct platform_device *pdev) | ||||
| { | ||||
|  | @ -202,15 +372,29 @@ static int max14577_regulator_probe(struct platform_device *pdev) | |||
| 	struct max14577_platform_data *pdata = dev_get_platdata(max14577->dev); | ||||
| 	int i, ret; | ||||
| 	struct regulator_config config = {}; | ||||
| 	const struct regulator_desc *supported_regulators; | ||||
| 	unsigned int supported_regulators_size; | ||||
| 	enum maxim_device_type dev_type = max14577->dev_type; | ||||
| 
 | ||||
| 	ret = max14577_regulator_dt_parse_pdata(pdev); | ||||
| 	ret = max14577_regulator_dt_parse_pdata(pdev, dev_type); | ||||
| 	if (ret) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	config.dev = &pdev->dev; | ||||
| 	config.regmap = max14577->regmap; | ||||
| 	switch (dev_type) { | ||||
| 	case MAXIM_DEVICE_TYPE_MAX77836: | ||||
| 		supported_regulators = max77836_supported_regulators; | ||||
| 		supported_regulators_size = ARRAY_SIZE(max77836_supported_regulators); | ||||
| 		break; | ||||
| 	case MAXIM_DEVICE_TYPE_MAX14577: | ||||
| 	default: | ||||
| 		supported_regulators = max14577_supported_regulators; | ||||
| 		supported_regulators_size = ARRAY_SIZE(max14577_supported_regulators); | ||||
| 	} | ||||
| 
 | ||||
| 	for (i = 0; i < ARRAY_SIZE(supported_regulators); i++) { | ||||
| 	config.dev = &pdev->dev; | ||||
| 	config.driver_data = max14577; | ||||
| 
 | ||||
| 	for (i = 0; i < supported_regulators_size; i++) { | ||||
| 		struct regulator_dev *regulator; | ||||
| 		/*
 | ||||
| 		 * Index of supported_regulators[] is also the id and must | ||||
|  | @ -220,17 +404,19 @@ static int max14577_regulator_probe(struct platform_device *pdev) | |||
| 			config.init_data = pdata->regulators[i].initdata; | ||||
| 			config.of_node = pdata->regulators[i].of_node; | ||||
| 		} else { | ||||
| 			config.init_data = match_init_data(i); | ||||
| 			config.of_node = match_of_node(i); | ||||
| 			config.init_data = match_init_data(i, dev_type); | ||||
| 			config.of_node = match_of_node(i, dev_type); | ||||
| 		} | ||||
| 		config.regmap = max14577_get_regmap(max14577, | ||||
| 				supported_regulators[i].id); | ||||
| 
 | ||||
| 		regulator = devm_regulator_register(&pdev->dev, | ||||
| 				&supported_regulators[i], &config); | ||||
| 		if (IS_ERR(regulator)) { | ||||
| 			ret = PTR_ERR(regulator); | ||||
| 			dev_err(&pdev->dev, | ||||
| 					"Regulator init failed for ID %d with error: %d\n", | ||||
| 					i, ret); | ||||
| 					"Regulator init failed for %d/%s with error: %d\n", | ||||
| 					i, supported_regulators[i].name, ret); | ||||
| 			return ret; | ||||
| 		} | ||||
| 	} | ||||
|  | @ -238,20 +424,41 @@ static int max14577_regulator_probe(struct platform_device *pdev) | |||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| static const struct platform_device_id max14577_regulator_id[] = { | ||||
| 	{ "max14577-regulator", MAXIM_DEVICE_TYPE_MAX14577, }, | ||||
| 	{ "max77836-regulator", MAXIM_DEVICE_TYPE_MAX77836, }, | ||||
| 	{ } | ||||
| }; | ||||
| MODULE_DEVICE_TABLE(platform, max14577_regulator_id); | ||||
| 
 | ||||
| static struct platform_driver max14577_regulator_driver = { | ||||
| 	.driver = { | ||||
| 		   .owner = THIS_MODULE, | ||||
| 		   .name = "max14577-regulator", | ||||
| 		   }, | ||||
| 	.probe	= max14577_regulator_probe, | ||||
| 	.probe		= max14577_regulator_probe, | ||||
| 	.id_table	= max14577_regulator_id, | ||||
| }; | ||||
| 
 | ||||
| static int __init max14577_regulator_init(void) | ||||
| { | ||||
| 	/* Check for valid values for charger */ | ||||
| 	BUILD_BUG_ON(MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START + | ||||
| 			MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_STEP * 0xf != | ||||
| 			MAX14577_REGULATOR_CURRENT_LIMIT_MAX); | ||||
| 	BUILD_BUG_ON(ARRAY_SIZE(supported_regulators) != MAX14577_REG_MAX); | ||||
| 	BUILD_BUG_ON(MAX77836_REGULATOR_CURRENT_LIMIT_HIGH_START + | ||||
| 			MAX77836_REGULATOR_CURRENT_LIMIT_HIGH_STEP * 0xf != | ||||
| 			MAX77836_REGULATOR_CURRENT_LIMIT_MAX); | ||||
| 	/* Valid charger current values must be provided for each chipset */ | ||||
| 	BUILD_BUG_ON(ARRAY_SIZE(maxim_charger_currents) != MAXIM_DEVICE_TYPE_NUM); | ||||
| 
 | ||||
| 	BUILD_BUG_ON(ARRAY_SIZE(max14577_supported_regulators) != MAX14577_REGULATOR_NUM); | ||||
| 	BUILD_BUG_ON(ARRAY_SIZE(max77836_supported_regulators) != MAX77836_REGULATOR_NUM); | ||||
| 
 | ||||
| 	BUILD_BUG_ON(MAX77836_REGULATOR_LDO_VOLTAGE_MIN + | ||||
| 			(MAX77836_REGULATOR_LDO_VOLTAGE_STEP * | ||||
| 			  (MAX77836_REGULATOR_LDO_VOLTAGE_STEPS_NUM - 1)) != | ||||
| 			MAX77836_REGULATOR_LDO_VOLTAGE_MAX); | ||||
| 
 | ||||
| 	return platform_driver_register(&max14577_regulator_driver); | ||||
| } | ||||
|  | @ -264,6 +471,6 @@ static void __exit max14577_regulator_exit(void) | |||
| module_exit(max14577_regulator_exit); | ||||
| 
 | ||||
| MODULE_AUTHOR("Krzysztof Kozlowski <k.kozlowski@samsung.com>"); | ||||
| MODULE_DESCRIPTION("MAXIM 14577 regulator driver"); | ||||
| MODULE_DESCRIPTION("Maxim 14577/77836 regulator driver"); | ||||
| MODULE_LICENSE("GPL"); | ||||
| MODULE_ALIAS("platform:max14577-regulator"); | ||||
|  |  | |||
|  | @ -655,7 +655,7 @@ static int uio_mmap_physical(struct vm_area_struct *vma) | |||
| 
 | ||||
| 	if (mem->addr & ~PAGE_MASK) | ||||
| 		return -ENODEV; | ||||
| 	if (vma->vm_end - vma->vm_start > mem->size) | ||||
| 	if (vma->vm_end - vma->vm_start > PAGE_ALIGN(mem->size)) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	vma->vm_ops = &uio_physical_vm_ops; | ||||
|  |  | |||
|  | @ -204,7 +204,7 @@ static int uio_dmem_genirq_probe(struct platform_device *pdev) | |||
| 		ret = platform_get_irq(pdev, 0); | ||||
| 		if (ret < 0) { | ||||
| 			dev_err(&pdev->dev, "failed to get IRQ\n"); | ||||
| 			goto bad0; | ||||
| 			goto bad1; | ||||
| 		} | ||||
| 		uioinfo->irq = ret; | ||||
| 	} | ||||
|  | @ -275,6 +275,7 @@ static int uio_dmem_genirq_probe(struct platform_device *pdev) | |||
| 	ret = uio_register_device(&pdev->dev, priv->uioinfo); | ||||
| 	if (ret) { | ||||
| 		dev_err(&pdev->dev, "unable to register uio device\n"); | ||||
| 		pm_runtime_disable(&pdev->dev); | ||||
| 		goto bad1; | ||||
| 	} | ||||
| 
 | ||||
|  | @ -282,7 +283,6 @@ static int uio_dmem_genirq_probe(struct platform_device *pdev) | |||
| 	return 0; | ||||
|  bad1: | ||||
| 	kfree(priv); | ||||
| 	pm_runtime_disable(&pdev->dev); | ||||
|  bad0: | ||||
| 	/* kfree uioinfo for OF */ | ||||
| 	if (pdev->dev.of_node) | ||||
|  |  | |||
|  | @ -1078,6 +1078,8 @@ static void w1_search_process(struct w1_master *dev, u8 search_type) | |||
|  * w1_process_callbacks() - execute each dev->async_list callback entry | ||||
|  * @dev: w1_master device | ||||
|  * | ||||
|  * The w1 master list_mutex must be held. | ||||
|  * | ||||
|  * Return: 1 if there were commands to executed 0 otherwise | ||||
|  */ | ||||
| int w1_process_callbacks(struct w1_master *dev) | ||||
|  |  | |||
|  | @ -203,7 +203,6 @@ enum w1_master_flags { | |||
|  * @search_id:		allows continuing a search | ||||
|  * @refcnt:		reference count | ||||
|  * @priv:		private data storage | ||||
|  * @priv_size:		size allocated | ||||
|  * @enable_pullup:	allows a strong pullup | ||||
|  * @pullup_duration:	time for the next strong pullup | ||||
|  * @flags:		one of w1_master_flags | ||||
|  | @ -214,7 +213,6 @@ enum w1_master_flags { | |||
|  * @dev:		sysfs device | ||||
|  * @bus_master:		io operations available | ||||
|  * @seq:		sequence number used for netlink broadcasts | ||||
|  * @portid:		destination for the current netlink command | ||||
|  */ | ||||
| struct w1_master | ||||
| { | ||||
|  | @ -241,7 +239,6 @@ struct w1_master | |||
| 	atomic_t		refcnt; | ||||
| 
 | ||||
| 	void			*priv; | ||||
| 	int			priv_size; | ||||
| 
 | ||||
| 	/** 5V strong pullup enabled flag, 1 enabled, zero disabled. */ | ||||
| 	int			enable_pullup; | ||||
|  | @ -260,11 +257,6 @@ struct w1_master | |||
| 	struct w1_bus_master	*bus_master; | ||||
| 
 | ||||
| 	u32			seq; | ||||
| 	/* port id to send netlink responses to.  The value is temporarily
 | ||||
| 	 * stored here while processing a message, set after locking the | ||||
| 	 * mutex, zero before unlocking the mutex. | ||||
| 	 */ | ||||
| 	u32			portid; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  |  | |||
|  | @ -219,9 +219,13 @@ void __w1_remove_master_device(struct w1_master *dev) | |||
| 
 | ||||
| 		if (msleep_interruptible(1000)) | ||||
| 			flush_signals(current); | ||||
| 		mutex_lock(&dev->list_mutex); | ||||
| 		w1_process_callbacks(dev); | ||||
| 		mutex_unlock(&dev->list_mutex); | ||||
| 	} | ||||
| 	mutex_lock(&dev->list_mutex); | ||||
| 	w1_process_callbacks(dev); | ||||
| 	mutex_unlock(&dev->list_mutex); | ||||
| 
 | ||||
| 	memset(&msg, 0, sizeof(msg)); | ||||
| 	msg.id.mst.id = dev->id; | ||||
|  |  | |||
|  | @ -29,51 +29,247 @@ | |||
| #include "w1_netlink.h" | ||||
| 
 | ||||
| #if defined(CONFIG_W1_CON) && (defined(CONFIG_CONNECTOR) || (defined(CONFIG_CONNECTOR_MODULE) && defined(CONFIG_W1_MODULE))) | ||||
| 
 | ||||
| #define MIN(a, b)                   (((a) < (b)) ? (a) : (b)) | ||||
| 
 | ||||
| /* Bundle together everything required to process a request in one memory
 | ||||
|  * allocation. | ||||
|  */ | ||||
| struct w1_cb_block { | ||||
| 	atomic_t refcnt; | ||||
| 	u32 portid; /* Sending process port ID */ | ||||
| 	/* maximum value for first_cn->len */ | ||||
| 	u16 maxlen; | ||||
| 	/* pointers to building up the reply message */ | ||||
| 	struct cn_msg *first_cn; /* fixed once the structure is populated */ | ||||
| 	struct cn_msg *cn; /* advances as cn_msg is appeneded */ | ||||
| 	struct w1_netlink_msg *msg; /* advances as w1_netlink_msg is appened */ | ||||
| 	struct w1_netlink_cmd *cmd; /* advances as cmds are appened */ | ||||
| 	struct w1_netlink_msg *cur_msg; /* currently message being processed */ | ||||
| 	/* copy of the original request follows */ | ||||
| 	struct cn_msg request_cn; | ||||
| 	/* followed by variable length:
 | ||||
| 	 * cn_msg, data (w1_netlink_msg and w1_netlink_cmd) | ||||
| 	 * one or more struct w1_cb_node | ||||
| 	 * reply first_cn, data (w1_netlink_msg and w1_netlink_cmd) | ||||
| 	 */ | ||||
| }; | ||||
| struct w1_cb_node { | ||||
| 	struct w1_async_cmd async; | ||||
| 	/* pointers within w1_cb_block and cn data */ | ||||
| 	struct w1_cb_block *block; | ||||
| 	struct w1_netlink_msg *msg; | ||||
| 	struct w1_slave *sl; | ||||
| 	struct w1_master *dev; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * w1_reply_len() - calculate current reply length, compare to maxlen | ||||
|  * @block: block to calculate | ||||
|  * | ||||
|  * Calculates the current message length including possible multiple | ||||
|  * cn_msg and data, excludes the first sizeof(struct cn_msg).  Direclty | ||||
|  * compariable to maxlen and usable to send the message. | ||||
|  */ | ||||
| static u16 w1_reply_len(struct w1_cb_block *block) | ||||
| { | ||||
| 	if (!block->cn) | ||||
| 		return 0; | ||||
| 	return (u8 *)block->cn - (u8 *)block->first_cn + block->cn->len; | ||||
| } | ||||
| 
 | ||||
| static void w1_unref_block(struct w1_cb_block *block) | ||||
| { | ||||
| 	if (atomic_sub_return(1, &block->refcnt) == 0) { | ||||
| 		u16 len = w1_reply_len(block); | ||||
| 		if (len) { | ||||
| 			cn_netlink_send_mult(block->first_cn, len, | ||||
| 				block->portid, 0, GFP_KERNEL); | ||||
| 		} | ||||
| 		kfree(block); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * w1_reply_make_space() - send message if needed to make space | ||||
|  * @block: block to make space on | ||||
|  * @space: how many bytes requested | ||||
|  * | ||||
|  * Verify there is enough room left for the caller to add "space" bytes to the | ||||
|  * message, if there isn't send the message and reset. | ||||
|  */ | ||||
| static void w1_reply_make_space(struct w1_cb_block *block, u16 space) | ||||
| { | ||||
| 	u16 len = w1_reply_len(block); | ||||
| 	if (len + space >= block->maxlen) { | ||||
| 		cn_netlink_send_mult(block->first_cn, len, block->portid, 0, GFP_KERNEL); | ||||
| 		block->first_cn->len = 0; | ||||
| 		block->cn = NULL; | ||||
| 		block->msg = NULL; | ||||
| 		block->cmd = NULL; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /* Early send when replies aren't bundled. */ | ||||
| static void w1_netlink_check_send(struct w1_cb_block *block) | ||||
| { | ||||
| 	if (!(block->request_cn.flags & W1_CN_BUNDLE) && block->cn) | ||||
| 		w1_reply_make_space(block, block->maxlen); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * w1_netlink_setup_msg() - prepare to write block->msg | ||||
|  * @block: block to operate on | ||||
|  * @ack: determines if cn can be reused | ||||
|  * | ||||
|  * block->cn will be setup with the correct ack, advancing if needed | ||||
|  * block->cn->len does not include space for block->msg | ||||
|  * block->msg advances but remains uninitialized | ||||
|  */ | ||||
| static void w1_netlink_setup_msg(struct w1_cb_block *block, u32 ack) | ||||
| { | ||||
| 	if (block->cn && block->cn->ack == ack) { | ||||
| 		block->msg = (struct w1_netlink_msg *)(block->cn->data + block->cn->len); | ||||
| 	} else { | ||||
| 		/* advance or set to data */ | ||||
| 		if (block->cn) | ||||
| 			block->cn = (struct cn_msg *)(block->cn->data + | ||||
| 				block->cn->len); | ||||
| 		else | ||||
| 			block->cn = block->first_cn; | ||||
| 
 | ||||
| 		memcpy(block->cn, &block->request_cn, sizeof(*block->cn)); | ||||
| 		block->cn->len = 0; | ||||
| 		block->cn->ack = ack; | ||||
| 		block->msg = (struct w1_netlink_msg *)block->cn->data; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /* Append cmd to msg, include cmd->data as well.  This is because
 | ||||
|  * any following data goes with the command and in the case of a read is | ||||
|  * the results. | ||||
|  */ | ||||
| static void w1_netlink_queue_cmd(struct w1_cb_block *block, | ||||
| 	struct w1_netlink_cmd *cmd) | ||||
| { | ||||
| 	u32 space; | ||||
| 	w1_reply_make_space(block, sizeof(struct cn_msg) + | ||||
| 		sizeof(struct w1_netlink_msg) + sizeof(*cmd) + cmd->len); | ||||
| 
 | ||||
| 	/* There's a status message sent after each command, so no point
 | ||||
| 	 * in trying to bundle this cmd after an existing one, because | ||||
| 	 * there won't be one.  Allocate and copy over a new cn_msg. | ||||
| 	 */ | ||||
| 	w1_netlink_setup_msg(block, block->request_cn.seq + 1); | ||||
| 	memcpy(block->msg, block->cur_msg, sizeof(*block->msg)); | ||||
| 	block->cn->len += sizeof(*block->msg); | ||||
| 	block->msg->len = 0; | ||||
| 	block->cmd = (struct w1_netlink_cmd *)(block->msg->data); | ||||
| 
 | ||||
| 	space = sizeof(*cmd) + cmd->len; | ||||
| 	if (block->cmd != cmd) | ||||
| 		memcpy(block->cmd, cmd, space); | ||||
| 	block->cn->len += space; | ||||
| 	block->msg->len += space; | ||||
| } | ||||
| 
 | ||||
| /* Append req_msg and req_cmd, no other commands and no data from req_cmd are
 | ||||
|  * copied. | ||||
|  */ | ||||
| static void w1_netlink_queue_status(struct w1_cb_block *block, | ||||
| 	struct w1_netlink_msg *req_msg, struct w1_netlink_cmd *req_cmd, | ||||
| 	int error) | ||||
| { | ||||
| 	u16 space = sizeof(struct cn_msg) + sizeof(*req_msg) + sizeof(*req_cmd); | ||||
| 	w1_reply_make_space(block, space); | ||||
| 	w1_netlink_setup_msg(block, block->request_cn.ack); | ||||
| 
 | ||||
| 	memcpy(block->msg, req_msg, sizeof(*req_msg)); | ||||
| 	block->cn->len += sizeof(*req_msg); | ||||
| 	block->msg->len = 0; | ||||
| 	block->msg->status = (u8)-error; | ||||
| 	if (req_cmd) { | ||||
| 		struct w1_netlink_cmd *cmd = (struct w1_netlink_cmd *)block->msg->data; | ||||
| 		memcpy(cmd, req_cmd, sizeof(*cmd)); | ||||
| 		block->cn->len += sizeof(*cmd); | ||||
| 		block->msg->len += sizeof(*cmd); | ||||
| 		cmd->len = 0; | ||||
| 	} | ||||
| 	w1_netlink_check_send(block); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * w1_netlink_send_error() - sends the error message now | ||||
|  * @cn: original cn_msg | ||||
|  * @msg: original w1_netlink_msg | ||||
|  * @portid: where to send it | ||||
|  * @error: error status | ||||
|  * | ||||
|  * Use when a block isn't available to queue the message to and cn, msg | ||||
|  * might not be contiguous. | ||||
|  */ | ||||
| static void w1_netlink_send_error(struct cn_msg *cn, struct w1_netlink_msg *msg, | ||||
| 	int portid, int error) | ||||
| { | ||||
| 	struct { | ||||
| 		struct cn_msg cn; | ||||
| 		struct w1_netlink_msg msg; | ||||
| 	} packet; | ||||
| 	memcpy(&packet.cn, cn, sizeof(packet.cn)); | ||||
| 	memcpy(&packet.msg, msg, sizeof(packet.msg)); | ||||
| 	packet.cn.len = sizeof(packet.msg); | ||||
| 	packet.msg.len = 0; | ||||
| 	packet.msg.status = (u8)-error; | ||||
| 	cn_netlink_send(&packet.cn, portid, 0, GFP_KERNEL); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * w1_netlink_send() - sends w1 netlink notifications | ||||
|  * @dev: w1_master the even is associated with or for | ||||
|  * @msg: w1_netlink_msg message to be sent | ||||
|  * | ||||
|  * This are notifications generated from the kernel. | ||||
|  */ | ||||
| void w1_netlink_send(struct w1_master *dev, struct w1_netlink_msg *msg) | ||||
| { | ||||
| 	char buf[sizeof(struct cn_msg) + sizeof(struct w1_netlink_msg)]; | ||||
| 	struct cn_msg *m = (struct cn_msg *)buf; | ||||
| 	struct w1_netlink_msg *w = (struct w1_netlink_msg *)(m+1); | ||||
| 	struct { | ||||
| 		struct cn_msg cn; | ||||
| 		struct w1_netlink_msg msg; | ||||
| 	} packet; | ||||
| 	memset(&packet, 0, sizeof(packet)); | ||||
| 
 | ||||
| 	memset(buf, 0, sizeof(buf)); | ||||
| 	packet.cn.id.idx = CN_W1_IDX; | ||||
| 	packet.cn.id.val = CN_W1_VAL; | ||||
| 
 | ||||
| 	m->id.idx = CN_W1_IDX; | ||||
| 	m->id.val = CN_W1_VAL; | ||||
| 	packet.cn.seq = dev->seq++; | ||||
| 	packet.cn.len = sizeof(*msg); | ||||
| 
 | ||||
| 	m->seq = dev->seq++; | ||||
| 	m->len = sizeof(struct w1_netlink_msg); | ||||
| 	memcpy(&packet.msg, msg, sizeof(*msg)); | ||||
| 	packet.msg.len = 0; | ||||
| 
 | ||||
| 	memcpy(w, msg, sizeof(struct w1_netlink_msg)); | ||||
| 
 | ||||
| 	cn_netlink_send(m, dev->portid, 0, GFP_KERNEL); | ||||
| 	cn_netlink_send(&packet.cn, 0, 0, GFP_KERNEL); | ||||
| } | ||||
| 
 | ||||
| static void w1_send_slave(struct w1_master *dev, u64 rn) | ||||
| { | ||||
| 	struct cn_msg *msg = dev->priv; | ||||
| 	struct w1_netlink_msg *hdr = (struct w1_netlink_msg *)(msg + 1); | ||||
| 	struct w1_netlink_cmd *cmd = (struct w1_netlink_cmd *)(hdr + 1); | ||||
| 	int avail; | ||||
| 	struct w1_cb_block *block = dev->priv; | ||||
| 	struct w1_netlink_cmd *cache_cmd = block->cmd; | ||||
| 	u64 *data; | ||||
| 
 | ||||
| 	avail = dev->priv_size - cmd->len; | ||||
| 	w1_reply_make_space(block, sizeof(*data)); | ||||
| 
 | ||||
| 	if (avail < 8) { | ||||
| 		msg->ack++; | ||||
| 		cn_netlink_send(msg, dev->portid, 0, GFP_KERNEL); | ||||
| 
 | ||||
| 		msg->len = sizeof(struct w1_netlink_msg) + | ||||
| 			sizeof(struct w1_netlink_cmd); | ||||
| 		hdr->len = sizeof(struct w1_netlink_cmd); | ||||
| 		cmd->len = 0; | ||||
| 	/* Add cmd back if the packet was sent */ | ||||
| 	if (!block->cmd) { | ||||
| 		cache_cmd->len = 0; | ||||
| 		w1_netlink_queue_cmd(block, cache_cmd); | ||||
| 	} | ||||
| 
 | ||||
| 	data = (void *)(cmd + 1) + cmd->len; | ||||
| 	data = (u64 *)(block->cmd->data + block->cmd->len); | ||||
| 
 | ||||
| 	*data = rn; | ||||
| 	cmd->len += 8; | ||||
| 	hdr->len += 8; | ||||
| 	msg->len += 8; | ||||
| 	block->cn->len += sizeof(*data); | ||||
| 	block->msg->len += sizeof(*data); | ||||
| 	block->cmd->len += sizeof(*data); | ||||
| } | ||||
| 
 | ||||
| static void w1_found_send_slave(struct w1_master *dev, u64 rn) | ||||
|  | @ -85,40 +281,15 @@ static void w1_found_send_slave(struct w1_master *dev, u64 rn) | |||
| } | ||||
| 
 | ||||
| /* Get the current slave list, or search (with or without alarm) */ | ||||
| static int w1_get_slaves(struct w1_master *dev, | ||||
| 		struct cn_msg *req_msg, struct w1_netlink_msg *req_hdr, | ||||
| 		struct w1_netlink_cmd *req_cmd) | ||||
| static int w1_get_slaves(struct w1_master *dev, struct w1_netlink_cmd *req_cmd) | ||||
| { | ||||
| 	struct cn_msg *msg; | ||||
| 	struct w1_netlink_msg *hdr; | ||||
| 	struct w1_netlink_cmd *cmd; | ||||
| 	struct w1_slave *sl; | ||||
| 
 | ||||
| 	msg = kzalloc(PAGE_SIZE, GFP_KERNEL); | ||||
| 	if (!msg) | ||||
| 		return -ENOMEM; | ||||
| 
 | ||||
| 	msg->id = req_msg->id; | ||||
| 	msg->seq = req_msg->seq; | ||||
| 	msg->ack = 0; | ||||
| 	msg->len = sizeof(struct w1_netlink_msg) + | ||||
| 		sizeof(struct w1_netlink_cmd); | ||||
| 
 | ||||
| 	hdr = (struct w1_netlink_msg *)(msg + 1); | ||||
| 	cmd = (struct w1_netlink_cmd *)(hdr + 1); | ||||
| 
 | ||||
| 	hdr->type = W1_MASTER_CMD; | ||||
| 	hdr->id = req_hdr->id; | ||||
| 	hdr->len = sizeof(struct w1_netlink_cmd); | ||||
| 
 | ||||
| 	cmd->cmd = req_cmd->cmd; | ||||
| 	cmd->len = 0; | ||||
| 
 | ||||
| 	dev->priv = msg; | ||||
| 	dev->priv_size = PAGE_SIZE - msg->len - sizeof(struct cn_msg); | ||||
| 	req_cmd->len = 0; | ||||
| 	w1_netlink_queue_cmd(dev->priv, req_cmd); | ||||
| 
 | ||||
| 	if (req_cmd->cmd == W1_CMD_LIST_SLAVES) { | ||||
| 		__u64 rn; | ||||
| 		u64 rn; | ||||
| 		mutex_lock(&dev->list_mutex); | ||||
| 		list_for_each_entry(sl, &dev->slist, w1_slave_entry) { | ||||
| 			memcpy(&rn, &sl->reg_num, sizeof(rn)); | ||||
|  | @ -126,73 +297,26 @@ static int w1_get_slaves(struct w1_master *dev, | |||
| 		} | ||||
| 		mutex_unlock(&dev->list_mutex); | ||||
| 	} else { | ||||
| 		w1_search_process_cb(dev, cmd->cmd == W1_CMD_ALARM_SEARCH ? | ||||
| 		w1_search_process_cb(dev, req_cmd->cmd == W1_CMD_ALARM_SEARCH ? | ||||
| 			W1_ALARM_SEARCH : W1_SEARCH, w1_found_send_slave); | ||||
| 	} | ||||
| 
 | ||||
| 	msg->ack = 0; | ||||
| 	cn_netlink_send(msg, dev->portid, 0, GFP_KERNEL); | ||||
| 
 | ||||
| 	dev->priv = NULL; | ||||
| 	dev->priv_size = 0; | ||||
| 
 | ||||
| 	kfree(msg); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int w1_send_read_reply(struct cn_msg *msg, struct w1_netlink_msg *hdr, | ||||
| 		struct w1_netlink_cmd *cmd, u32 portid) | ||||
| { | ||||
| 	void *data; | ||||
| 	struct w1_netlink_msg *h; | ||||
| 	struct w1_netlink_cmd *c; | ||||
| 	struct cn_msg *cm; | ||||
| 	int err; | ||||
| 
 | ||||
| 	data = kzalloc(sizeof(struct cn_msg) + | ||||
| 			sizeof(struct w1_netlink_msg) + | ||||
| 			sizeof(struct w1_netlink_cmd) + | ||||
| 			cmd->len, GFP_KERNEL); | ||||
| 	if (!data) | ||||
| 		return -ENOMEM; | ||||
| 
 | ||||
| 	cm = (struct cn_msg *)(data); | ||||
| 	h = (struct w1_netlink_msg *)(cm + 1); | ||||
| 	c = (struct w1_netlink_cmd *)(h + 1); | ||||
| 
 | ||||
| 	memcpy(cm, msg, sizeof(struct cn_msg)); | ||||
| 	memcpy(h, hdr, sizeof(struct w1_netlink_msg)); | ||||
| 	memcpy(c, cmd, sizeof(struct w1_netlink_cmd)); | ||||
| 
 | ||||
| 	cm->ack = msg->seq+1; | ||||
| 	cm->len = sizeof(struct w1_netlink_msg) + | ||||
| 		sizeof(struct w1_netlink_cmd) + cmd->len; | ||||
| 
 | ||||
| 	h->len = sizeof(struct w1_netlink_cmd) + cmd->len; | ||||
| 
 | ||||
| 	memcpy(c->data, cmd->data, c->len); | ||||
| 
 | ||||
| 	err = cn_netlink_send(cm, portid, 0, GFP_KERNEL); | ||||
| 
 | ||||
| 	kfree(data); | ||||
| 
 | ||||
| 	return err; | ||||
| } | ||||
| 
 | ||||
| static int w1_process_command_io(struct w1_master *dev, struct cn_msg *msg, | ||||
| 		struct w1_netlink_msg *hdr, struct w1_netlink_cmd *cmd) | ||||
| static int w1_process_command_io(struct w1_master *dev, | ||||
| 	struct w1_netlink_cmd *cmd) | ||||
| { | ||||
| 	int err = 0; | ||||
| 
 | ||||
| 	switch (cmd->cmd) { | ||||
| 	case W1_CMD_TOUCH: | ||||
| 		w1_touch_block(dev, cmd->data, cmd->len); | ||||
| 		w1_send_read_reply(msg, hdr, cmd, dev->portid); | ||||
| 		w1_netlink_queue_cmd(dev->priv, cmd); | ||||
| 		break; | ||||
| 	case W1_CMD_READ: | ||||
| 		w1_read_block(dev, cmd->data, cmd->len); | ||||
| 		w1_send_read_reply(msg, hdr, cmd, dev->portid); | ||||
| 		w1_netlink_queue_cmd(dev->priv, cmd); | ||||
| 		break; | ||||
| 	case W1_CMD_WRITE: | ||||
| 		w1_write_block(dev, cmd->data, cmd->len); | ||||
|  | @ -206,14 +330,13 @@ static int w1_process_command_io(struct w1_master *dev, struct cn_msg *msg, | |||
| } | ||||
| 
 | ||||
| static int w1_process_command_addremove(struct w1_master *dev, | ||||
| 	struct cn_msg *msg, struct w1_netlink_msg *hdr, | ||||
| 	struct w1_netlink_cmd *cmd) | ||||
| { | ||||
| 	struct w1_slave *sl; | ||||
| 	int err = 0; | ||||
| 	struct w1_reg_num *id; | ||||
| 
 | ||||
| 	if (cmd->len != 8) | ||||
| 	if (cmd->len != sizeof(*id)) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	id = (struct w1_reg_num *)cmd->data; | ||||
|  | @ -241,7 +364,6 @@ static int w1_process_command_addremove(struct w1_master *dev, | |||
| } | ||||
| 
 | ||||
| static int w1_process_command_master(struct w1_master *dev, | ||||
| 	struct cn_msg *req_msg, struct w1_netlink_msg *req_hdr, | ||||
| 	struct w1_netlink_cmd *req_cmd) | ||||
| { | ||||
| 	int err = -EINVAL; | ||||
|  | @ -254,13 +376,13 @@ static int w1_process_command_master(struct w1_master *dev, | |||
| 	case W1_CMD_ALARM_SEARCH: | ||||
| 	case W1_CMD_LIST_SLAVES: | ||||
| 		mutex_unlock(&dev->bus_mutex); | ||||
| 		err = w1_get_slaves(dev, req_msg, req_hdr, req_cmd); | ||||
| 		err = w1_get_slaves(dev, req_cmd); | ||||
| 		mutex_lock(&dev->bus_mutex); | ||||
| 		break; | ||||
| 	case W1_CMD_READ: | ||||
| 	case W1_CMD_WRITE: | ||||
| 	case W1_CMD_TOUCH: | ||||
| 		err = w1_process_command_io(dev, req_msg, req_hdr, req_cmd); | ||||
| 		err = w1_process_command_io(dev, req_cmd); | ||||
| 		break; | ||||
| 	case W1_CMD_RESET: | ||||
| 		err = w1_reset_bus(dev); | ||||
|  | @ -269,8 +391,7 @@ static int w1_process_command_master(struct w1_master *dev, | |||
| 	case W1_CMD_SLAVE_REMOVE: | ||||
| 		mutex_unlock(&dev->bus_mutex); | ||||
| 		mutex_lock(&dev->mutex); | ||||
| 		err = w1_process_command_addremove(dev, req_msg, req_hdr, | ||||
| 			req_cmd); | ||||
| 		err = w1_process_command_addremove(dev, req_cmd); | ||||
| 		mutex_unlock(&dev->mutex); | ||||
| 		mutex_lock(&dev->bus_mutex); | ||||
| 		break; | ||||
|  | @ -282,22 +403,21 @@ static int w1_process_command_master(struct w1_master *dev, | |||
| 	return err; | ||||
| } | ||||
| 
 | ||||
| static int w1_process_command_slave(struct w1_slave *sl, struct cn_msg *msg, | ||||
| 		struct w1_netlink_msg *hdr, struct w1_netlink_cmd *cmd) | ||||
| static int w1_process_command_slave(struct w1_slave *sl, | ||||
| 		struct w1_netlink_cmd *cmd) | ||||
| { | ||||
| 	dev_dbg(&sl->master->dev, "%s: %02x.%012llx.%02x: cmd=%02x, len=%u.\n", | ||||
| 		__func__, sl->reg_num.family, (unsigned long long)sl->reg_num.id, | ||||
| 		sl->reg_num.crc, cmd->cmd, cmd->len); | ||||
| 
 | ||||
| 	return w1_process_command_io(sl->master, msg, hdr, cmd); | ||||
| 	return w1_process_command_io(sl->master, cmd); | ||||
| } | ||||
| 
 | ||||
| static int w1_process_command_root(struct cn_msg *msg, | ||||
| 	struct w1_netlink_msg *mcmd, u32 portid) | ||||
| static int w1_process_command_root(struct cn_msg *req_cn, u32 portid) | ||||
| { | ||||
| 	struct w1_master *m; | ||||
| 	struct w1_master *dev; | ||||
| 	struct cn_msg *cn; | ||||
| 	struct w1_netlink_msg *w; | ||||
| 	struct w1_netlink_msg *msg; | ||||
| 	u32 *id; | ||||
| 
 | ||||
| 	cn = kmalloc(PAGE_SIZE, GFP_KERNEL); | ||||
|  | @ -307,32 +427,30 @@ static int w1_process_command_root(struct cn_msg *msg, | |||
| 	cn->id.idx = CN_W1_IDX; | ||||
| 	cn->id.val = CN_W1_VAL; | ||||
| 
 | ||||
| 	cn->seq = msg->seq; | ||||
| 	cn->ack = 1; | ||||
| 	cn->seq = req_cn->seq; | ||||
| 	cn->ack = req_cn->seq + 1; | ||||
| 	cn->len = sizeof(struct w1_netlink_msg); | ||||
| 	w = (struct w1_netlink_msg *)(cn + 1); | ||||
| 	msg = (struct w1_netlink_msg *)cn->data; | ||||
| 
 | ||||
| 	w->type = W1_LIST_MASTERS; | ||||
| 	w->status = 0; | ||||
| 	w->len = 0; | ||||
| 	id = (u32 *)(w + 1); | ||||
| 	msg->type = W1_LIST_MASTERS; | ||||
| 	msg->status = 0; | ||||
| 	msg->len = 0; | ||||
| 	id = (u32 *)msg->data; | ||||
| 
 | ||||
| 	mutex_lock(&w1_mlock); | ||||
| 	list_for_each_entry(m, &w1_masters, w1_master_entry) { | ||||
| 	list_for_each_entry(dev, &w1_masters, w1_master_entry) { | ||||
| 		if (cn->len + sizeof(*id) > PAGE_SIZE - sizeof(struct cn_msg)) { | ||||
| 			cn_netlink_send(cn, portid, 0, GFP_KERNEL); | ||||
| 			cn->ack++; | ||||
| 			cn->len = sizeof(struct w1_netlink_msg); | ||||
| 			w->len = 0; | ||||
| 			id = (u32 *)(w + 1); | ||||
| 			msg->len = 0; | ||||
| 			id = (u32 *)msg->data; | ||||
| 		} | ||||
| 
 | ||||
| 		*id = m->id; | ||||
| 		w->len += sizeof(*id); | ||||
| 		*id = dev->id; | ||||
| 		msg->len += sizeof(*id); | ||||
| 		cn->len += sizeof(*id); | ||||
| 		id++; | ||||
| 	} | ||||
| 	cn->ack = 0; | ||||
| 	cn_netlink_send(cn, portid, 0, GFP_KERNEL); | ||||
| 	mutex_unlock(&w1_mlock); | ||||
| 
 | ||||
|  | @ -340,100 +458,44 @@ static int w1_process_command_root(struct cn_msg *msg, | |||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int w1_netlink_send_error(struct cn_msg *rcmsg, struct w1_netlink_msg *rmsg, | ||||
| 		struct w1_netlink_cmd *rcmd, int portid, int error) | ||||
| { | ||||
| 	struct cn_msg *cmsg; | ||||
| 	struct w1_netlink_msg *msg; | ||||
| 	struct w1_netlink_cmd *cmd; | ||||
| 
 | ||||
| 	cmsg = kzalloc(sizeof(*msg) + sizeof(*cmd) + sizeof(*cmsg), GFP_KERNEL); | ||||
| 	if (!cmsg) | ||||
| 		return -ENOMEM; | ||||
| 
 | ||||
| 	msg = (struct w1_netlink_msg *)(cmsg + 1); | ||||
| 	cmd = (struct w1_netlink_cmd *)(msg + 1); | ||||
| 
 | ||||
| 	memcpy(cmsg, rcmsg, sizeof(*cmsg)); | ||||
| 	cmsg->len = sizeof(*msg); | ||||
| 
 | ||||
| 	memcpy(msg, rmsg, sizeof(*msg)); | ||||
| 	msg->len = 0; | ||||
| 	msg->status = (short)-error; | ||||
| 
 | ||||
| 	if (rcmd) { | ||||
| 		memcpy(cmd, rcmd, sizeof(*cmd)); | ||||
| 		cmd->len = 0; | ||||
| 		msg->len += sizeof(*cmd); | ||||
| 		cmsg->len += sizeof(*cmd); | ||||
| 	} | ||||
| 
 | ||||
| 	error = cn_netlink_send(cmsg, portid, 0, GFP_KERNEL); | ||||
| 	kfree(cmsg); | ||||
| 
 | ||||
| 	return error; | ||||
| } | ||||
| 
 | ||||
| /* Bundle together a reference count, the full message, and broken out
 | ||||
|  * commands to be executed on each w1 master kthread in one memory allocation. | ||||
|  */ | ||||
| struct w1_cb_block { | ||||
| 	atomic_t refcnt; | ||||
| 	u32 portid; /* Sending process port ID */ | ||||
| 	struct cn_msg msg; | ||||
| 	/* cn_msg data */ | ||||
| 	/* one or more variable length struct w1_cb_node */ | ||||
| }; | ||||
| struct w1_cb_node { | ||||
| 	struct w1_async_cmd async; | ||||
| 	/* pointers within w1_cb_block and msg data */ | ||||
| 	struct w1_cb_block *block; | ||||
| 	struct w1_netlink_msg *m; | ||||
| 	struct w1_slave *sl; | ||||
| 	struct w1_master *dev; | ||||
| }; | ||||
| 
 | ||||
| static void w1_process_cb(struct w1_master *dev, struct w1_async_cmd *async_cmd) | ||||
| { | ||||
| 	struct w1_cb_node *node = container_of(async_cmd, struct w1_cb_node, | ||||
| 		async); | ||||
| 	u16 mlen = node->m->len; | ||||
| 	u8 *cmd_data = node->m->data; | ||||
| 	u16 mlen = node->msg->len; | ||||
| 	u16 len; | ||||
| 	int err = 0; | ||||
| 	struct w1_slave *sl = node->sl; | ||||
| 	struct w1_netlink_cmd *cmd = NULL; | ||||
| 	struct w1_netlink_cmd *cmd = (struct w1_netlink_cmd *)node->msg->data; | ||||
| 
 | ||||
| 	mutex_lock(&dev->bus_mutex); | ||||
| 	dev->portid = node->block->portid; | ||||
| 	dev->priv = node->block; | ||||
| 	if (sl && w1_reset_select_slave(sl)) | ||||
| 		err = -ENODEV; | ||||
| 	node->block->cur_msg = node->msg; | ||||
| 
 | ||||
| 	while (mlen && !err) { | ||||
| 		cmd = (struct w1_netlink_cmd *)cmd_data; | ||||
| 
 | ||||
| 		if (cmd->len + sizeof(struct w1_netlink_cmd) > mlen) { | ||||
| 			err = -E2BIG; | ||||
| 			break; | ||||
| 		} | ||||
| 
 | ||||
| 		if (sl) | ||||
| 			err = w1_process_command_slave(sl, &node->block->msg, | ||||
| 				node->m, cmd); | ||||
| 			err = w1_process_command_slave(sl, cmd); | ||||
| 		else | ||||
| 			err = w1_process_command_master(dev, &node->block->msg, | ||||
| 				node->m, cmd); | ||||
| 			err = w1_process_command_master(dev, cmd); | ||||
| 		w1_netlink_check_send(node->block); | ||||
| 
 | ||||
| 		w1_netlink_send_error(&node->block->msg, node->m, cmd, | ||||
| 			node->block->portid, err); | ||||
| 		w1_netlink_queue_status(node->block, node->msg, cmd, err); | ||||
| 		err = 0; | ||||
| 
 | ||||
| 		cmd_data += cmd->len + sizeof(struct w1_netlink_cmd); | ||||
| 		mlen -= cmd->len + sizeof(struct w1_netlink_cmd); | ||||
| 		len = sizeof(*cmd) + cmd->len; | ||||
| 		cmd = (struct w1_netlink_cmd *)((u8 *)cmd + len); | ||||
| 		mlen -= len; | ||||
| 	} | ||||
| 
 | ||||
| 	if (!cmd || err) | ||||
| 		w1_netlink_send_error(&node->block->msg, node->m, cmd, | ||||
| 			node->block->portid, err); | ||||
| 		w1_netlink_queue_status(node->block, node->msg, cmd, err); | ||||
| 
 | ||||
| 	/* ref taken in w1_search_slave or w1_search_master_id when building
 | ||||
| 	 * the block | ||||
|  | @ -442,99 +504,186 @@ static void w1_process_cb(struct w1_master *dev, struct w1_async_cmd *async_cmd) | |||
| 		w1_unref_slave(sl); | ||||
| 	else | ||||
| 		atomic_dec(&dev->refcnt); | ||||
| 	dev->portid = 0; | ||||
| 	dev->priv = NULL; | ||||
| 	mutex_unlock(&dev->bus_mutex); | ||||
| 
 | ||||
| 	mutex_lock(&dev->list_mutex); | ||||
| 	list_del(&async_cmd->async_entry); | ||||
| 	mutex_unlock(&dev->list_mutex); | ||||
| 
 | ||||
| 	if (atomic_sub_return(1, &node->block->refcnt) == 0) | ||||
| 		kfree(node->block); | ||||
| 	w1_unref_block(node->block); | ||||
| } | ||||
| 
 | ||||
| static void w1_cn_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp) | ||||
| static void w1_list_count_cmds(struct w1_netlink_msg *msg, int *cmd_count, | ||||
| 	u16 *slave_len) | ||||
| { | ||||
| 	struct w1_netlink_msg *m = (struct w1_netlink_msg *)(msg + 1); | ||||
| 	struct w1_netlink_cmd *cmd = (struct w1_netlink_cmd *)msg->data; | ||||
| 	u16 mlen = msg->len; | ||||
| 	u16 len; | ||||
| 	int slave_list = 0; | ||||
| 	while (mlen) { | ||||
| 		if (cmd->len + sizeof(struct w1_netlink_cmd) > mlen) | ||||
| 			break; | ||||
| 
 | ||||
| 		switch (cmd->cmd) { | ||||
| 		case W1_CMD_SEARCH: | ||||
| 		case W1_CMD_ALARM_SEARCH: | ||||
| 		case W1_CMD_LIST_SLAVES: | ||||
| 			++slave_list; | ||||
| 		} | ||||
| 		++*cmd_count; | ||||
| 		len = sizeof(*cmd) + cmd->len; | ||||
| 		cmd = (struct w1_netlink_cmd *)((u8 *)cmd + len); | ||||
| 		mlen -= len; | ||||
| 	} | ||||
| 
 | ||||
| 	if (slave_list) { | ||||
| 		struct w1_master *dev = w1_search_master_id(msg->id.mst.id); | ||||
| 		if (dev) { | ||||
| 			/* Bytes, and likely an overstimate, and if it isn't
 | ||||
| 			 * the results can still be split between packets. | ||||
| 			 */ | ||||
| 			*slave_len += sizeof(struct w1_reg_num) * slave_list * | ||||
| 				(dev->slave_count + dev->max_slave_count); | ||||
| 			/* search incremented it */ | ||||
| 			atomic_dec(&dev->refcnt); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static void w1_cn_callback(struct cn_msg *cn, struct netlink_skb_parms *nsp) | ||||
| { | ||||
| 	struct w1_netlink_msg *msg = (struct w1_netlink_msg *)(cn + 1); | ||||
| 	struct w1_slave *sl; | ||||
| 	struct w1_master *dev; | ||||
| 	u16 msg_len; | ||||
| 	u16 slave_len = 0; | ||||
| 	int err = 0; | ||||
| 	struct w1_cb_block *block = NULL; | ||||
| 	struct w1_cb_node *node = NULL; | ||||
| 	int node_count = 0; | ||||
| 	int cmd_count = 0; | ||||
| 
 | ||||
| 	/* If any unknown flag is set let the application know, that way
 | ||||
| 	 * applications can detect the absence of features in kernels that | ||||
| 	 * don't know about them.  http://lwn.net/Articles/587527/
 | ||||
| 	 */ | ||||
| 	if (cn->flags & ~(W1_CN_BUNDLE)) { | ||||
| 		w1_netlink_send_error(cn, msg, nsp->portid, -EINVAL); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	/* Count the number of master or slave commands there are to allocate
 | ||||
| 	 * space for one cb_node each. | ||||
| 	 */ | ||||
| 	msg_len = msg->len; | ||||
| 	msg_len = cn->len; | ||||
| 	while (msg_len && !err) { | ||||
| 		if (m->len + sizeof(struct w1_netlink_msg) > msg_len) { | ||||
| 		if (msg->len + sizeof(struct w1_netlink_msg) > msg_len) { | ||||
| 			err = -E2BIG; | ||||
| 			break; | ||||
| 		} | ||||
| 
 | ||||
| 		if (m->type == W1_MASTER_CMD || m->type == W1_SLAVE_CMD) | ||||
| 		/* count messages for nodes and allocate any additional space
 | ||||
| 		 * required for slave lists | ||||
| 		 */ | ||||
| 		if (msg->type == W1_MASTER_CMD || msg->type == W1_SLAVE_CMD) { | ||||
| 			++node_count; | ||||
| 			w1_list_count_cmds(msg, &cmd_count, &slave_len); | ||||
| 		} | ||||
| 
 | ||||
| 		msg_len -= sizeof(struct w1_netlink_msg) + m->len; | ||||
| 		m = (struct w1_netlink_msg *)(((u8 *)m) + | ||||
| 			sizeof(struct w1_netlink_msg) + m->len); | ||||
| 		msg_len -= sizeof(struct w1_netlink_msg) + msg->len; | ||||
| 		msg = (struct w1_netlink_msg *)(((u8 *)msg) + | ||||
| 			sizeof(struct w1_netlink_msg) + msg->len); | ||||
| 	} | ||||
| 	m = (struct w1_netlink_msg *)(msg + 1); | ||||
| 	msg = (struct w1_netlink_msg *)(cn + 1); | ||||
| 	if (node_count) { | ||||
| 		/* msg->len doesn't include itself */ | ||||
| 		long size = sizeof(struct w1_cb_block) + msg->len + | ||||
| 			node_count*sizeof(struct w1_cb_node); | ||||
| 		block = kmalloc(size, GFP_KERNEL); | ||||
| 		int size; | ||||
| 		u16 reply_size = sizeof(*cn) + cn->len + slave_len; | ||||
| 		if (cn->flags & W1_CN_BUNDLE) { | ||||
| 			/* bundling duplicats some of the messages */ | ||||
| 			reply_size += 2 * cmd_count * (sizeof(struct cn_msg) + | ||||
| 				sizeof(struct w1_netlink_msg) + | ||||
| 				sizeof(struct w1_netlink_cmd)); | ||||
| 		} | ||||
| 		reply_size = MIN(CONNECTOR_MAX_MSG_SIZE, reply_size); | ||||
| 
 | ||||
| 		/* allocate space for the block, a copy of the original message,
 | ||||
| 		 * one node per cmd to point into the original message, | ||||
| 		 * space for replies which is the original message size plus | ||||
| 		 * space for any list slave data and status messages | ||||
| 		 * cn->len doesn't include itself which is part of the block | ||||
| 		 * */ | ||||
| 		size =  /* block + original message */ | ||||
| 			sizeof(struct w1_cb_block) + sizeof(*cn) + cn->len + | ||||
| 			/* space for nodes */ | ||||
| 			node_count * sizeof(struct w1_cb_node) + | ||||
| 			/* replies */ | ||||
| 			sizeof(struct cn_msg) + reply_size; | ||||
| 		block = kzalloc(size, GFP_KERNEL); | ||||
| 		if (!block) { | ||||
| 			w1_netlink_send_error(msg, m, NULL, nsp->portid, | ||||
| 				-ENOMEM); | ||||
| 			/* if the system is already out of memory,
 | ||||
| 			 * (A) will this work, and (B) would it be better | ||||
| 			 * to not try? | ||||
| 			 */ | ||||
| 			w1_netlink_send_error(cn, msg, nsp->portid, -ENOMEM); | ||||
| 			return; | ||||
| 		} | ||||
| 		atomic_set(&block->refcnt, 1); | ||||
| 		block->portid = nsp->portid; | ||||
| 		memcpy(&block->msg, msg, sizeof(*msg) + msg->len); | ||||
| 		node = (struct w1_cb_node *)((u8 *)block->msg.data + msg->len); | ||||
| 		memcpy(&block->request_cn, cn, sizeof(*cn) + cn->len); | ||||
| 		node = (struct w1_cb_node *)(block->request_cn.data + cn->len); | ||||
| 
 | ||||
| 		/* Sneeky, when not bundling, reply_size is the allocated space
 | ||||
| 		 * required for the reply, cn_msg isn't part of maxlen so | ||||
| 		 * it should be reply_size - sizeof(struct cn_msg), however | ||||
| 		 * when checking if there is enough space, w1_reply_make_space | ||||
| 		 * is called with the full message size including cn_msg, | ||||
| 		 * because it isn't known at that time if an additional cn_msg | ||||
| 		 * will need to be allocated.  So an extra cn_msg is added | ||||
| 		 * above in "size". | ||||
| 		 */ | ||||
| 		block->maxlen = reply_size; | ||||
| 		block->first_cn = (struct cn_msg *)(node + node_count); | ||||
| 		memset(block->first_cn, 0, sizeof(*block->first_cn)); | ||||
| 	} | ||||
| 
 | ||||
| 	msg_len = msg->len; | ||||
| 	msg_len = cn->len; | ||||
| 	while (msg_len && !err) { | ||||
| 
 | ||||
| 		dev = NULL; | ||||
| 		sl = NULL; | ||||
| 
 | ||||
| 		if (m->len + sizeof(struct w1_netlink_msg) > msg_len) { | ||||
| 		if (msg->len + sizeof(struct w1_netlink_msg) > msg_len) { | ||||
| 			err = -E2BIG; | ||||
| 			break; | ||||
| 		} | ||||
| 
 | ||||
| 		/* execute on this thread, no need to process later */ | ||||
| 		if (m->type == W1_LIST_MASTERS) { | ||||
| 			err = w1_process_command_root(msg, m, nsp->portid); | ||||
| 		if (msg->type == W1_LIST_MASTERS) { | ||||
| 			err = w1_process_command_root(cn, nsp->portid); | ||||
| 			goto out_cont; | ||||
| 		} | ||||
| 
 | ||||
| 		/* All following message types require additional data,
 | ||||
| 		 * check here before references are taken. | ||||
| 		 */ | ||||
| 		if (!m->len) { | ||||
| 		if (!msg->len) { | ||||
| 			err = -EPROTO; | ||||
| 			goto out_cont; | ||||
| 		} | ||||
| 
 | ||||
| 		/* both search calls take reference counts */ | ||||
| 		if (m->type == W1_MASTER_CMD) { | ||||
| 			dev = w1_search_master_id(m->id.mst.id); | ||||
| 		} else if (m->type == W1_SLAVE_CMD) { | ||||
| 			sl = w1_search_slave((struct w1_reg_num *)m->id.id); | ||||
| 		/* both search calls take references */ | ||||
| 		if (msg->type == W1_MASTER_CMD) { | ||||
| 			dev = w1_search_master_id(msg->id.mst.id); | ||||
| 		} else if (msg->type == W1_SLAVE_CMD) { | ||||
| 			sl = w1_search_slave((struct w1_reg_num *)msg->id.id); | ||||
| 			if (sl) | ||||
| 				dev = sl->master; | ||||
| 		} else { | ||||
| 			printk(KERN_NOTICE | ||||
| 				"%s: msg: %x.%x, wrong type: %u, len: %u.\n", | ||||
| 				__func__, msg->id.idx, msg->id.val, | ||||
| 				m->type, m->len); | ||||
| 				"%s: cn: %x.%x, wrong type: %u, len: %u.\n", | ||||
| 				__func__, cn->id.idx, cn->id.val, | ||||
| 				msg->type, msg->len); | ||||
| 			err = -EPROTO; | ||||
| 			goto out_cont; | ||||
| 		} | ||||
|  | @ -549,8 +698,8 @@ static void w1_cn_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp) | |||
| 		atomic_inc(&block->refcnt); | ||||
| 		node->async.cb = w1_process_cb; | ||||
| 		node->block = block; | ||||
| 		node->m = (struct w1_netlink_msg *)((u8 *)&block->msg + | ||||
| 			(size_t)((u8 *)m - (u8 *)msg)); | ||||
| 		node->msg = (struct w1_netlink_msg *)((u8 *)&block->request_cn + | ||||
| 			(size_t)((u8 *)msg - (u8 *)cn)); | ||||
| 		node->sl = sl; | ||||
| 		node->dev = dev; | ||||
| 
 | ||||
|  | @ -561,11 +710,15 @@ static void w1_cn_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp) | |||
| 		++node; | ||||
| 
 | ||||
| out_cont: | ||||
| 		/* Can't queue because that modifies block and another
 | ||||
| 		 * thread could be processing the messages by now and | ||||
| 		 * there isn't a lock, send directly. | ||||
| 		 */ | ||||
| 		if (err) | ||||
| 			w1_netlink_send_error(msg, m, NULL, nsp->portid, err); | ||||
| 		msg_len -= sizeof(struct w1_netlink_msg) + m->len; | ||||
| 		m = (struct w1_netlink_msg *)(((u8 *)m) + | ||||
| 			sizeof(struct w1_netlink_msg) + m->len); | ||||
| 			w1_netlink_send_error(cn, msg, nsp->portid, err); | ||||
| 		msg_len -= sizeof(struct w1_netlink_msg) + msg->len; | ||||
| 		msg = (struct w1_netlink_msg *)(((u8 *)msg) + | ||||
| 			sizeof(struct w1_netlink_msg) + msg->len); | ||||
| 
 | ||||
| 		/*
 | ||||
| 		 * Let's allow requests for nonexisting devices. | ||||
|  | @ -573,8 +726,8 @@ out_cont: | |||
| 		if (err == -ENODEV) | ||||
| 			err = 0; | ||||
| 	} | ||||
| 	if (block && atomic_sub_return(1, &block->refcnt) == 0) | ||||
| 		kfree(block); | ||||
| 	if (block) | ||||
| 		w1_unref_block(block); | ||||
| } | ||||
| 
 | ||||
| int w1_init_netlink(void) | ||||
|  | @ -591,7 +744,7 @@ void w1_fini_netlink(void) | |||
| 	cn_del_callback(&w1_id); | ||||
| } | ||||
| #else | ||||
| void w1_netlink_send(struct w1_master *dev, struct w1_netlink_msg *msg) | ||||
| void w1_netlink_send(struct w1_master *dev, struct w1_netlink_msg *cn) | ||||
| { | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -27,6 +27,17 @@ | |||
| 
 | ||||
| #include "w1.h" | ||||
| 
 | ||||
| /**
 | ||||
|  * enum w1_cn_msg_flags - bitfield flags for struct cn_msg.flags | ||||
|  * | ||||
|  * @W1_CN_BUNDLE: Request bundling replies into fewer messagse.  Be prepared | ||||
|  * to handle multiple struct cn_msg, struct w1_netlink_msg, and | ||||
|  * struct w1_netlink_cmd in one packet. | ||||
|  */ | ||||
| enum w1_cn_msg_flags { | ||||
| 	W1_CN_BUNDLE = 1, | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * enum w1_netlink_message_types - message type | ||||
|  * | ||||
|  | @ -49,6 +60,19 @@ enum w1_netlink_message_types { | |||
| 	W1_LIST_MASTERS, | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * struct w1_netlink_msg - holds w1 message type, id, and result | ||||
|  * | ||||
|  * @type: one of enum w1_netlink_message_types | ||||
|  * @status: kernel feedback for success 0 or errno failure value | ||||
|  * @len: length of data following w1_netlink_msg | ||||
|  * @id: union holding master bus id (msg.id) and slave device id (id[8]). | ||||
|  * @data: start address of any following data | ||||
|  * | ||||
|  * The base message structure for w1 messages over netlink. | ||||
|  * The netlink connector data sequence is, struct nlmsghdr, struct cn_msg, | ||||
|  * then one or more struct w1_netlink_msg (each with optional data). | ||||
|  */ | ||||
| struct w1_netlink_msg | ||||
| { | ||||
| 	__u8				type; | ||||
|  | @ -66,6 +90,7 @@ struct w1_netlink_msg | |||
| 
 | ||||
| /**
 | ||||
|  * enum w1_commands - commands available for master or slave operations | ||||
|  * | ||||
|  * @W1_CMD_READ: read len bytes | ||||
|  * @W1_CMD_WRITE: write len bytes | ||||
|  * @W1_CMD_SEARCH: initiate a standard search, returns only the slave | ||||
|  | @ -93,6 +118,17 @@ enum w1_commands { | |||
| 	W1_CMD_MAX | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * struct w1_netlink_cmd - holds the command and data | ||||
|  * | ||||
|  * @cmd: one of enum w1_commands | ||||
|  * @res: reserved | ||||
|  * @len: length of data following w1_netlink_cmd | ||||
|  * @data: start address of any following data | ||||
|  * | ||||
|  * One or more struct w1_netlink_cmd is placed starting at w1_netlink_msg.data | ||||
|  * each with optional data. | ||||
|  */ | ||||
| struct w1_netlink_cmd | ||||
| { | ||||
| 	__u8				cmd; | ||||
|  |  | |||
|  | @ -71,6 +71,7 @@ struct cn_dev { | |||
| int cn_add_callback(struct cb_id *id, const char *name, | ||||
| 		    void (*callback)(struct cn_msg *, struct netlink_skb_parms *)); | ||||
| void cn_del_callback(struct cb_id *); | ||||
| int cn_netlink_send_mult(struct cn_msg *msg, u16 len, u32 portid, u32 group, gfp_t gfp_mask); | ||||
| int cn_netlink_send(struct cn_msg *msg, u32 portid, u32 group, gfp_t gfp_mask); | ||||
| 
 | ||||
| int cn_queue_add_callback(struct cn_queue_dev *dev, const char *name, | ||||
|  |  | |||
|  | @ -185,8 +185,21 @@ struct extcon_specific_cable_nb { | |||
|  */ | ||||
| extern int extcon_dev_register(struct extcon_dev *edev); | ||||
| extern void extcon_dev_unregister(struct extcon_dev *edev); | ||||
| extern int devm_extcon_dev_register(struct device *dev, | ||||
| 				    struct extcon_dev *edev); | ||||
| extern void devm_extcon_dev_unregister(struct device *dev, | ||||
| 				       struct extcon_dev *edev); | ||||
| extern struct extcon_dev *extcon_get_extcon_dev(const char *extcon_name); | ||||
| 
 | ||||
| /*
 | ||||
|  * Following APIs control the memory of extcon device. | ||||
|  */ | ||||
| extern struct extcon_dev *extcon_dev_allocate(const char **cables); | ||||
| extern void extcon_dev_free(struct extcon_dev *edev); | ||||
| extern struct extcon_dev *devm_extcon_dev_allocate(struct device *dev, | ||||
| 						   const char **cables); | ||||
| extern void devm_extcon_dev_free(struct device *dev, struct extcon_dev *edev); | ||||
| 
 | ||||
| /*
 | ||||
|  * get/set/update_state access the 32b encoded state value, which represents | ||||
|  * states of all possible cables of the multistate port. For example, if one | ||||
|  | @ -254,6 +267,30 @@ static inline int extcon_dev_register(struct extcon_dev *edev) | |||
| 
 | ||||
| static inline void extcon_dev_unregister(struct extcon_dev *edev) { } | ||||
| 
 | ||||
| static inline int devm_extcon_dev_register(struct device *dev, | ||||
| 					   struct extcon_dev *edev) | ||||
| { | ||||
| 	return -EINVAL; | ||||
| } | ||||
| 
 | ||||
| static inline void devm_extcon_dev_unregister(struct device *dev, | ||||
| 					      struct extcon_dev *edev) { } | ||||
| 
 | ||||
| static inline struct extcon_dev *extcon_dev_allocate(const char **cables) | ||||
| { | ||||
| 	return ERR_PTR(-ENOSYS); | ||||
| } | ||||
| 
 | ||||
| static inline void extcon_dev_free(struct extcon_dev *edev) { } | ||||
| 
 | ||||
| static inline struct extcon_dev *devm_extcon_dev_allocate(struct device *dev, | ||||
| 							  const char **cables) | ||||
| { | ||||
| 	return ERR_PTR(-ENOSYS); | ||||
| } | ||||
| 
 | ||||
| static inline void devm_extcon_dev_free(struct extcon_dev *edev) { } | ||||
| 
 | ||||
| static inline u32 extcon_get_state(struct extcon_dev *edev) | ||||
| { | ||||
| 	return 0; | ||||
|  |  | |||
|  | @ -696,6 +696,8 @@ struct vmbus_channel { | |||
| 	 * preserve the earlier behavior. | ||||
| 	 */ | ||||
| 	u32 target_vp; | ||||
| 	/* The corresponding CPUID in the guest */ | ||||
| 	u32 target_cpu; | ||||
| 	/*
 | ||||
| 	 * Support for sub-channels. For high performance devices, | ||||
| 	 * it will be useful to have multiple sub-channels to support | ||||
|  | @ -732,6 +734,11 @@ struct vmbus_channel { | |||
| 	 * Support per-channel state for use by vmbus drivers. | ||||
| 	 */ | ||||
| 	void *per_channel_state; | ||||
| 	/*
 | ||||
| 	 * To support per-cpu lookup mapping of relid to channel, | ||||
| 	 * link up channels based on their CPU affinity. | ||||
| 	 */ | ||||
| 	struct list_head percpu_list; | ||||
| }; | ||||
| 
 | ||||
| static inline void set_channel_read_state(struct vmbus_channel *c, bool state) | ||||
|  |  | |||
|  | @ -16,6 +16,7 @@ | |||
| #include <linux/irqreturn.h> | ||||
| 
 | ||||
| struct mcb_driver; | ||||
| struct mcb_device; | ||||
| 
 | ||||
| /**
 | ||||
|  * struct mcb_bus - MEN Chameleon Bus | ||||
|  | @ -23,11 +24,14 @@ struct mcb_driver; | |||
|  * @dev: pointer to carrier device | ||||
|  * @children: the child busses | ||||
|  * @bus_nr: mcb bus number | ||||
|  * @get_irq: callback to get IRQ number | ||||
|  */ | ||||
| struct mcb_bus { | ||||
| 	struct list_head children; | ||||
| 	struct device dev; | ||||
| 	struct device *carrier; | ||||
| 	int bus_nr; | ||||
| 	int (*get_irq)(struct mcb_device *dev); | ||||
| }; | ||||
| #define to_mcb_bus(b) container_of((b), struct mcb_bus, dev) | ||||
| 
 | ||||
|  | @ -105,7 +109,7 @@ extern void mcb_unregister_driver(struct mcb_driver *driver); | |||
| 	module_driver(__mcb_driver, mcb_register_driver, mcb_unregister_driver); | ||||
| extern void mcb_bus_add_devices(const struct mcb_bus *bus); | ||||
| extern int mcb_device_register(struct mcb_bus *bus, struct mcb_device *dev); | ||||
| extern struct mcb_bus *mcb_alloc_bus(void); | ||||
| extern struct mcb_bus *mcb_alloc_bus(struct device *carrier); | ||||
| extern struct mcb_bus *mcb_bus_get(struct mcb_bus *bus); | ||||
| extern void mcb_bus_put(struct mcb_bus *bus); | ||||
| extern struct mcb_device *mcb_alloc_dev(struct mcb_bus *bus); | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| /*
 | ||||
|  * max14577-private.h - Common API for the Maxim 14577 internal sub chip | ||||
|  * max14577-private.h - Common API for the Maxim 14577/77836 internal sub chip | ||||
|  * | ||||
|  * Copyright (C) 2013 Samsung Electrnoics | ||||
|  * Copyright (C) 2014 Samsung Electrnoics | ||||
|  * Chanwoo Choi <cw00.choi@samsung.com> | ||||
|  * Krzysztof Kozlowski <k.kozlowski@samsung.com> | ||||
|  * | ||||
|  | @ -22,9 +22,19 @@ | |||
| #include <linux/i2c.h> | ||||
| #include <linux/regmap.h> | ||||
| 
 | ||||
| #define MAX14577_REG_INVALID		(0xff) | ||||
| #define I2C_ADDR_PMIC	(0x46 >> 1) | ||||
| #define I2C_ADDR_MUIC	(0x4A >> 1) | ||||
| #define I2C_ADDR_FG	(0x6C >> 1) | ||||
| 
 | ||||
| /* Slave addr = 0x4A: Interrupt */ | ||||
| enum maxim_device_type { | ||||
| 	MAXIM_DEVICE_TYPE_UNKNOWN	= 0, | ||||
| 	MAXIM_DEVICE_TYPE_MAX14577, | ||||
| 	MAXIM_DEVICE_TYPE_MAX77836, | ||||
| 
 | ||||
| 	MAXIM_DEVICE_TYPE_NUM, | ||||
| }; | ||||
| 
 | ||||
| /* Slave addr = 0x4A: MUIC and Charger */ | ||||
| enum max14577_reg { | ||||
| 	MAX14577_REG_DEVICEID		= 0x00, | ||||
| 	MAX14577_REG_INT1		= 0x01, | ||||
|  | @ -74,20 +84,22 @@ enum max14577_muic_charger_type { | |||
| }; | ||||
| 
 | ||||
| /* MAX14577 interrupts */ | ||||
| #define INT1_ADC_MASK			(0x1 << 0) | ||||
| #define INT1_ADCLOW_MASK		(0x1 << 1) | ||||
| #define INT1_ADCERR_MASK		(0x1 << 2) | ||||
| #define MAX14577_INT1_ADC_MASK		BIT(0) | ||||
| #define MAX14577_INT1_ADCLOW_MASK	BIT(1) | ||||
| #define MAX14577_INT1_ADCERR_MASK	BIT(2) | ||||
| #define MAX77836_INT1_ADC1K_MASK	BIT(3) | ||||
| 
 | ||||
| #define INT2_CHGTYP_MASK		(0x1 << 0) | ||||
| #define INT2_CHGDETRUN_MASK		(0x1 << 1) | ||||
| #define INT2_DCDTMR_MASK		(0x1 << 2) | ||||
| #define INT2_DBCHG_MASK			(0x1 << 3) | ||||
| #define INT2_VBVOLT_MASK		(0x1 << 4) | ||||
| #define MAX14577_INT2_CHGTYP_MASK	BIT(0) | ||||
| #define MAX14577_INT2_CHGDETRUN_MASK	BIT(1) | ||||
| #define MAX14577_INT2_DCDTMR_MASK	BIT(2) | ||||
| #define MAX14577_INT2_DBCHG_MASK	BIT(3) | ||||
| #define MAX14577_INT2_VBVOLT_MASK	BIT(4) | ||||
| #define MAX77836_INT2_VIDRM_MASK	BIT(5) | ||||
| 
 | ||||
| #define INT3_EOC_MASK			(0x1 << 0) | ||||
| #define INT3_CGMBC_MASK			(0x1 << 1) | ||||
| #define INT3_OVP_MASK			(0x1 << 2) | ||||
| #define INT3_MBCCHGERR_MASK		(0x1 << 3) | ||||
| #define MAX14577_INT3_EOC_MASK		BIT(0) | ||||
| #define MAX14577_INT3_CGMBC_MASK	BIT(1) | ||||
| #define MAX14577_INT3_OVP_MASK		BIT(2) | ||||
| #define MAX14577_INT3_MBCCHGERR_MASK	BIT(3) | ||||
| 
 | ||||
| /* MAX14577 DEVICE ID register */ | ||||
| #define DEVID_VENDORID_SHIFT		0 | ||||
|  | @ -99,9 +111,11 @@ enum max14577_muic_charger_type { | |||
| #define STATUS1_ADC_SHIFT		0 | ||||
| #define STATUS1_ADCLOW_SHIFT		5 | ||||
| #define STATUS1_ADCERR_SHIFT		6 | ||||
| #define MAX77836_STATUS1_ADC1K_SHIFT	7 | ||||
| #define STATUS1_ADC_MASK		(0x1f << STATUS1_ADC_SHIFT) | ||||
| #define STATUS1_ADCLOW_MASK		(0x1 << STATUS1_ADCLOW_SHIFT) | ||||
| #define STATUS1_ADCERR_MASK		(0x1 << STATUS1_ADCERR_SHIFT) | ||||
| #define STATUS1_ADCLOW_MASK		BIT(STATUS1_ADCLOW_SHIFT) | ||||
| #define STATUS1_ADCERR_MASK		BIT(STATUS1_ADCERR_SHIFT) | ||||
| #define MAX77836_STATUS1_ADC1K_MASK	BIT(MAX77836_STATUS1_ADC1K_SHIFT) | ||||
| 
 | ||||
| /* MAX14577 STATUS2 register */ | ||||
| #define STATUS2_CHGTYP_SHIFT		0 | ||||
|  | @ -109,11 +123,13 @@ enum max14577_muic_charger_type { | |||
| #define STATUS2_DCDTMR_SHIFT		4 | ||||
| #define STATUS2_DBCHG_SHIFT		5 | ||||
| #define STATUS2_VBVOLT_SHIFT		6 | ||||
| #define MAX77836_STATUS2_VIDRM_SHIFT	7 | ||||
| #define STATUS2_CHGTYP_MASK		(0x7 << STATUS2_CHGTYP_SHIFT) | ||||
| #define STATUS2_CHGDETRUN_MASK		(0x1 << STATUS2_CHGDETRUN_SHIFT) | ||||
| #define STATUS2_DCDTMR_MASK		(0x1 << STATUS2_DCDTMR_SHIFT) | ||||
| #define STATUS2_DBCHG_MASK		(0x1 << STATUS2_DBCHG_SHIFT) | ||||
| #define STATUS2_VBVOLT_MASK		(0x1 << STATUS2_VBVOLT_SHIFT) | ||||
| #define STATUS2_CHGDETRUN_MASK		BIT(STATUS2_CHGDETRUN_SHIFT) | ||||
| #define STATUS2_DCDTMR_MASK		BIT(STATUS2_DCDTMR_SHIFT) | ||||
| #define STATUS2_DBCHG_MASK		BIT(STATUS2_DBCHG_SHIFT) | ||||
| #define STATUS2_VBVOLT_MASK		BIT(STATUS2_VBVOLT_SHIFT) | ||||
| #define MAX77836_STATUS2_VIDRM_MASK	BIT(MAX77836_STATUS2_VIDRM_SHIFT) | ||||
| 
 | ||||
| /* MAX14577 CONTROL1 register */ | ||||
| #define COMN1SW_SHIFT			0 | ||||
|  | @ -122,8 +138,8 @@ enum max14577_muic_charger_type { | |||
| #define IDBEN_SHIFT			7 | ||||
| #define COMN1SW_MASK			(0x7 << COMN1SW_SHIFT) | ||||
| #define COMP2SW_MASK			(0x7 << COMP2SW_SHIFT) | ||||
| #define MICEN_MASK			(0x1 << MICEN_SHIFT) | ||||
| #define IDBEN_MASK			(0x1 << IDBEN_SHIFT) | ||||
| #define MICEN_MASK			BIT(MICEN_SHIFT) | ||||
| #define IDBEN_MASK			BIT(IDBEN_SHIFT) | ||||
| #define CLEAR_IDBEN_MICEN_MASK		(COMN1SW_MASK | COMP2SW_MASK) | ||||
| #define CTRL1_SW_USB			((1 << COMP2SW_SHIFT) \ | ||||
| 						| (1 << COMN1SW_SHIFT)) | ||||
|  | @ -143,14 +159,14 @@ enum max14577_muic_charger_type { | |||
| #define CTRL2_ACCDET_SHIFT		(5) | ||||
| #define CTRL2_USBCPINT_SHIFT		(6) | ||||
| #define CTRL2_RCPS_SHIFT		(7) | ||||
| #define CTRL2_LOWPWR_MASK		(0x1 << CTRL2_LOWPWR_SHIFT) | ||||
| #define CTRL2_ADCEN_MASK		(0x1 << CTRL2_ADCEN_SHIFT) | ||||
| #define CTRL2_CPEN_MASK			(0x1 << CTRL2_CPEN_SHIFT) | ||||
| #define CTRL2_SFOUTASRT_MASK		(0x1 << CTRL2_SFOUTASRT_SHIFT) | ||||
| #define CTRL2_SFOUTORD_MASK		(0x1 << CTRL2_SFOUTORD_SHIFT) | ||||
| #define CTRL2_ACCDET_MASK		(0x1 << CTRL2_ACCDET_SHIFT) | ||||
| #define CTRL2_USBCPINT_MASK		(0x1 << CTRL2_USBCPINT_SHIFT) | ||||
| #define CTRL2_RCPS_MASK			(0x1 << CTR2_RCPS_SHIFT) | ||||
| #define CTRL2_LOWPWR_MASK		BIT(CTRL2_LOWPWR_SHIFT) | ||||
| #define CTRL2_ADCEN_MASK		BIT(CTRL2_ADCEN_SHIFT) | ||||
| #define CTRL2_CPEN_MASK			BIT(CTRL2_CPEN_SHIFT) | ||||
| #define CTRL2_SFOUTASRT_MASK		BIT(CTRL2_SFOUTASRT_SHIFT) | ||||
| #define CTRL2_SFOUTORD_MASK		BIT(CTRL2_SFOUTORD_SHIFT) | ||||
| #define CTRL2_ACCDET_MASK		BIT(CTRL2_ACCDET_SHIFT) | ||||
| #define CTRL2_USBCPINT_MASK		BIT(CTRL2_USBCPINT_SHIFT) | ||||
| #define CTRL2_RCPS_MASK			BIT(CTRL2_RCPS_SHIFT) | ||||
| 
 | ||||
| #define CTRL2_CPEN1_LOWPWR0 ((1 << CTRL2_CPEN_SHIFT) | \ | ||||
| 				(0 << CTRL2_LOWPWR_SHIFT)) | ||||
|  | @ -198,14 +214,14 @@ enum max14577_charger_reg { | |||
| #define CDETCTRL1_DBEXIT_SHIFT		5 | ||||
| #define CDETCTRL1_DBIDLE_SHIFT		6 | ||||
| #define CDETCTRL1_CDPDET_SHIFT		7 | ||||
| #define CDETCTRL1_CHGDETEN_MASK		(0x1 << CDETCTRL1_CHGDETEN_SHIFT) | ||||
| #define CDETCTRL1_CHGTYPMAN_MASK	(0x1 << CDETCTRL1_CHGTYPMAN_SHIFT) | ||||
| #define CDETCTRL1_DCDEN_MASK		(0x1 << CDETCTRL1_DCDEN_SHIFT) | ||||
| #define CDETCTRL1_DCD2SCT_MASK		(0x1 << CDETCTRL1_DCD2SCT_SHIFT) | ||||
| #define CDETCTRL1_DCHKTM_MASK		(0x1 << CDETCTRL1_DCHKTM_SHIFT) | ||||
| #define CDETCTRL1_DBEXIT_MASK		(0x1 << CDETCTRL1_DBEXIT_SHIFT) | ||||
| #define CDETCTRL1_DBIDLE_MASK		(0x1 << CDETCTRL1_DBIDLE_SHIFT) | ||||
| #define CDETCTRL1_CDPDET_MASK		(0x1 << CDETCTRL1_CDPDET_SHIFT) | ||||
| #define CDETCTRL1_CHGDETEN_MASK		BIT(CDETCTRL1_CHGDETEN_SHIFT) | ||||
| #define CDETCTRL1_CHGTYPMAN_MASK	BIT(CDETCTRL1_CHGTYPMAN_SHIFT) | ||||
| #define CDETCTRL1_DCDEN_MASK		BIT(CDETCTRL1_DCDEN_SHIFT) | ||||
| #define CDETCTRL1_DCD2SCT_MASK		BIT(CDETCTRL1_DCD2SCT_SHIFT) | ||||
| #define CDETCTRL1_DCHKTM_MASK		BIT(CDETCTRL1_DCHKTM_SHIFT) | ||||
| #define CDETCTRL1_DBEXIT_MASK		BIT(CDETCTRL1_DBEXIT_SHIFT) | ||||
| #define CDETCTRL1_DBIDLE_MASK		BIT(CDETCTRL1_DBIDLE_SHIFT) | ||||
| #define CDETCTRL1_CDPDET_MASK		BIT(CDETCTRL1_CDPDET_SHIFT) | ||||
| 
 | ||||
| /* MAX14577 CHGCTRL1 register */ | ||||
| #define CHGCTRL1_TCHW_SHIFT		4 | ||||
|  | @ -213,9 +229,9 @@ enum max14577_charger_reg { | |||
| 
 | ||||
| /* MAX14577 CHGCTRL2 register */ | ||||
| #define CHGCTRL2_MBCHOSTEN_SHIFT	6 | ||||
| #define CHGCTRL2_MBCHOSTEN_MASK		(0x1 << CHGCTRL2_MBCHOSTEN_SHIFT) | ||||
| #define CHGCTRL2_MBCHOSTEN_MASK		BIT(CHGCTRL2_MBCHOSTEN_SHIFT) | ||||
| #define CHGCTRL2_VCHGR_RC_SHIFT		7 | ||||
| #define CHGCTRL2_VCHGR_RC_MASK		(0x1 << CHGCTRL2_VCHGR_RC_SHIFT) | ||||
| #define CHGCTRL2_VCHGR_RC_MASK		BIT(CHGCTRL2_VCHGR_RC_SHIFT) | ||||
| 
 | ||||
| /* MAX14577 CHGCTRL3 register */ | ||||
| #define CHGCTRL3_MBCCVWRC_SHIFT		0 | ||||
|  | @ -225,7 +241,7 @@ enum max14577_charger_reg { | |||
| #define CHGCTRL4_MBCICHWRCH_SHIFT	0 | ||||
| #define CHGCTRL4_MBCICHWRCH_MASK	(0xf << CHGCTRL4_MBCICHWRCH_SHIFT) | ||||
| #define CHGCTRL4_MBCICHWRCL_SHIFT	4 | ||||
| #define CHGCTRL4_MBCICHWRCL_MASK	(0x1 << CHGCTRL4_MBCICHWRCL_SHIFT) | ||||
| #define CHGCTRL4_MBCICHWRCL_MASK	BIT(CHGCTRL4_MBCICHWRCL_SHIFT) | ||||
| 
 | ||||
| /* MAX14577 CHGCTRL5 register */ | ||||
| #define CHGCTRL5_EOCS_SHIFT		0 | ||||
|  | @ -233,7 +249,7 @@ enum max14577_charger_reg { | |||
| 
 | ||||
| /* MAX14577 CHGCTRL6 register */ | ||||
| #define CHGCTRL6_AUTOSTOP_SHIFT		5 | ||||
| #define CHGCTRL6_AUTOSTOP_MASK		(0x1 << CHGCTRL6_AUTOSTOP_SHIFT) | ||||
| #define CHGCTRL6_AUTOSTOP_MASK		BIT(CHGCTRL6_AUTOSTOP_SHIFT) | ||||
| 
 | ||||
| /* MAX14577 CHGCTRL7 register */ | ||||
| #define CHGCTRL7_OTPCGHCVS_SHIFT	0 | ||||
|  | @ -245,14 +261,111 @@ enum max14577_charger_reg { | |||
| #define MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_STEP	 50000 | ||||
| #define MAX14577_REGULATOR_CURRENT_LIMIT_MAX		950000 | ||||
| 
 | ||||
| /* MAX77836 regulator current limits (as in CHGCTRL4 register), uA */ | ||||
| #define MAX77836_REGULATOR_CURRENT_LIMIT_MIN		 45000 | ||||
| #define MAX77836_REGULATOR_CURRENT_LIMIT_HIGH_START	100000 | ||||
| #define MAX77836_REGULATOR_CURRENT_LIMIT_HIGH_STEP	 25000 | ||||
| #define MAX77836_REGULATOR_CURRENT_LIMIT_MAX		475000 | ||||
| 
 | ||||
| /* MAX14577 regulator SFOUT LDO voltage, fixed, uV */ | ||||
| #define MAX14577_REGULATOR_SAFEOUT_VOLTAGE		4900000 | ||||
| 
 | ||||
| /* MAX77836 regulator LDOx voltage, uV */ | ||||
| #define MAX77836_REGULATOR_LDO_VOLTAGE_MIN		800000 | ||||
| #define MAX77836_REGULATOR_LDO_VOLTAGE_MAX		3950000 | ||||
| #define MAX77836_REGULATOR_LDO_VOLTAGE_STEP		50000 | ||||
| #define MAX77836_REGULATOR_LDO_VOLTAGE_STEPS_NUM	64 | ||||
| 
 | ||||
| /* Slave addr = 0x46: PMIC */ | ||||
| enum max77836_pmic_reg { | ||||
| 	MAX77836_PMIC_REG_PMIC_ID		= 0x20, | ||||
| 	MAX77836_PMIC_REG_PMIC_REV		= 0x21, | ||||
| 	MAX77836_PMIC_REG_INTSRC		= 0x22, | ||||
| 	MAX77836_PMIC_REG_INTSRC_MASK		= 0x23, | ||||
| 	MAX77836_PMIC_REG_TOPSYS_INT		= 0x24, | ||||
| 	MAX77836_PMIC_REG_TOPSYS_INT_MASK	= 0x26, | ||||
| 	MAX77836_PMIC_REG_TOPSYS_STAT		= 0x28, | ||||
| 	MAX77836_PMIC_REG_MRSTB_CNTL		= 0x2A, | ||||
| 	MAX77836_PMIC_REG_LSCNFG		= 0x2B, | ||||
| 
 | ||||
| 	MAX77836_LDO_REG_CNFG1_LDO1		= 0x51, | ||||
| 	MAX77836_LDO_REG_CNFG2_LDO1		= 0x52, | ||||
| 	MAX77836_LDO_REG_CNFG1_LDO2		= 0x53, | ||||
| 	MAX77836_LDO_REG_CNFG2_LDO2		= 0x54, | ||||
| 	MAX77836_LDO_REG_CNFG_LDO_BIAS		= 0x55, | ||||
| 
 | ||||
| 	MAX77836_COMP_REG_COMP1			= 0x60, | ||||
| 
 | ||||
| 	MAX77836_PMIC_REG_END, | ||||
| }; | ||||
| 
 | ||||
| #define MAX77836_INTSRC_MASK_TOP_INT_SHIFT	1 | ||||
| #define MAX77836_INTSRC_MASK_MUIC_CHG_INT_SHIFT	3 | ||||
| #define MAX77836_INTSRC_MASK_TOP_INT_MASK	BIT(MAX77836_INTSRC_MASK_TOP_INT_SHIFT) | ||||
| #define MAX77836_INTSRC_MASK_MUIC_CHG_INT_MASK	BIT(MAX77836_INTSRC_MASK_MUIC_CHG_INT_SHIFT) | ||||
| 
 | ||||
| /* MAX77836 PMIC interrupts */ | ||||
| #define MAX77836_TOPSYS_INT_T120C_SHIFT		0 | ||||
| #define MAX77836_TOPSYS_INT_T140C_SHIFT		1 | ||||
| #define MAX77836_TOPSYS_INT_T120C_MASK		BIT(MAX77836_TOPSYS_INT_T120C_SHIFT) | ||||
| #define MAX77836_TOPSYS_INT_T140C_MASK		BIT(MAX77836_TOPSYS_INT_T140C_SHIFT) | ||||
| 
 | ||||
| /* LDO1/LDO2 CONFIG1 register */ | ||||
| #define MAX77836_CNFG1_LDO_PWRMD_SHIFT		6 | ||||
| #define MAX77836_CNFG1_LDO_TV_SHIFT		0 | ||||
| #define MAX77836_CNFG1_LDO_PWRMD_MASK		(0x3 << MAX77836_CNFG1_LDO_PWRMD_SHIFT) | ||||
| #define MAX77836_CNFG1_LDO_TV_MASK		(0x3f << MAX77836_CNFG1_LDO_TV_SHIFT) | ||||
| 
 | ||||
| /* LDO1/LDO2 CONFIG2 register */ | ||||
| #define MAX77836_CNFG2_LDO_OVCLMPEN_SHIFT	7 | ||||
| #define MAX77836_CNFG2_LDO_ALPMEN_SHIFT		6 | ||||
| #define MAX77836_CNFG2_LDO_COMP_SHIFT		4 | ||||
| #define MAX77836_CNFG2_LDO_POK_SHIFT		3 | ||||
| #define MAX77836_CNFG2_LDO_ADE_SHIFT		1 | ||||
| #define MAX77836_CNFG2_LDO_SS_SHIFT		0 | ||||
| #define MAX77836_CNFG2_LDO_OVCLMPEN_MASK	BIT(MAX77836_CNFG2_LDO_OVCLMPEN_SHIFT) | ||||
| #define MAX77836_CNFG2_LDO_ALPMEN_MASK		BIT(MAX77836_CNFG2_LDO_ALPMEN_SHIFT) | ||||
| #define MAX77836_CNFG2_LDO_COMP_MASK		(0x3 << MAX77836_CNFG2_LDO_COMP_SHIFT) | ||||
| #define MAX77836_CNFG2_LDO_POK_MASK		BIT(MAX77836_CNFG2_LDO_POK_SHIFT) | ||||
| #define MAX77836_CNFG2_LDO_ADE_MASK		BIT(MAX77836_CNFG2_LDO_ADE_SHIFT) | ||||
| #define MAX77836_CNFG2_LDO_SS_MASK		BIT(MAX77836_CNFG2_LDO_SS_SHIFT) | ||||
| 
 | ||||
| /* Slave addr = 0x6C: Fuel-Gauge/Battery */ | ||||
| enum max77836_fg_reg { | ||||
| 	MAX77836_FG_REG_VCELL_MSB	= 0x02, | ||||
| 	MAX77836_FG_REG_VCELL_LSB	= 0x03, | ||||
| 	MAX77836_FG_REG_SOC_MSB		= 0x04, | ||||
| 	MAX77836_FG_REG_SOC_LSB		= 0x05, | ||||
| 	MAX77836_FG_REG_MODE_H		= 0x06, | ||||
| 	MAX77836_FG_REG_MODE_L		= 0x07, | ||||
| 	MAX77836_FG_REG_VERSION_MSB	= 0x08, | ||||
| 	MAX77836_FG_REG_VERSION_LSB	= 0x09, | ||||
| 	MAX77836_FG_REG_HIBRT_H		= 0x0A, | ||||
| 	MAX77836_FG_REG_HIBRT_L		= 0x0B, | ||||
| 	MAX77836_FG_REG_CONFIG_H	= 0x0C, | ||||
| 	MAX77836_FG_REG_CONFIG_L	= 0x0D, | ||||
| 	MAX77836_FG_REG_VALRT_MIN	= 0x14, | ||||
| 	MAX77836_FG_REG_VALRT_MAX	= 0x15, | ||||
| 	MAX77836_FG_REG_CRATE_MSB	= 0x16, | ||||
| 	MAX77836_FG_REG_CRATE_LSB	= 0x17, | ||||
| 	MAX77836_FG_REG_VRESET		= 0x18, | ||||
| 	MAX77836_FG_REG_FGID		= 0x19, | ||||
| 	MAX77836_FG_REG_STATUS_H	= 0x1A, | ||||
| 	MAX77836_FG_REG_STATUS_L	= 0x1B, | ||||
| 	/*
 | ||||
| 	 * TODO: TABLE registers | ||||
| 	 * TODO: CMD register | ||||
| 	 */ | ||||
| 
 | ||||
| 	MAX77836_FG_REG_END, | ||||
| }; | ||||
| 
 | ||||
| enum max14577_irq { | ||||
| 	/* INT1 */ | ||||
| 	MAX14577_IRQ_INT1_ADC, | ||||
| 	MAX14577_IRQ_INT1_ADCLOW, | ||||
| 	MAX14577_IRQ_INT1_ADCERR, | ||||
| 	MAX77836_IRQ_INT1_ADC1K, | ||||
| 
 | ||||
| 	/* INT2 */ | ||||
| 	MAX14577_IRQ_INT2_CHGTYP, | ||||
|  | @ -260,6 +373,7 @@ enum max14577_irq { | |||
| 	MAX14577_IRQ_INT2_DCDTMR, | ||||
| 	MAX14577_IRQ_INT2_DBCHG, | ||||
| 	MAX14577_IRQ_INT2_VBVOLT, | ||||
| 	MAX77836_IRQ_INT2_VIDRM, | ||||
| 
 | ||||
| 	/* INT3 */ | ||||
| 	MAX14577_IRQ_INT3_EOC, | ||||
|  | @ -267,21 +381,25 @@ enum max14577_irq { | |||
| 	MAX14577_IRQ_INT3_OVP, | ||||
| 	MAX14577_IRQ_INT3_MBCCHGERR, | ||||
| 
 | ||||
| 	/* TOPSYS_INT, only MAX77836 */ | ||||
| 	MAX77836_IRQ_TOPSYS_T140C, | ||||
| 	MAX77836_IRQ_TOPSYS_T120C, | ||||
| 
 | ||||
| 	MAX14577_IRQ_NUM, | ||||
| }; | ||||
| 
 | ||||
| struct max14577 { | ||||
| 	struct device *dev; | ||||
| 	struct i2c_client *i2c; /* Slave addr = 0x4A */ | ||||
| 	struct i2c_client *i2c_pmic; /* Slave addr = 0x46 */ | ||||
| 	enum maxim_device_type dev_type; | ||||
| 
 | ||||
| 	struct regmap *regmap; | ||||
| 	struct regmap *regmap; /* For MUIC and Charger */ | ||||
| 	struct regmap *regmap_pmic; | ||||
| 
 | ||||
| 	struct regmap_irq_chip_data *irq_data; | ||||
| 	struct regmap_irq_chip_data *irq_data; /* For MUIC and Charger */ | ||||
| 	struct regmap_irq_chip_data *irq_data_pmic; | ||||
| 	int irq; | ||||
| 
 | ||||
| 	/* Device ID */ | ||||
| 	u8 vendor_id;	/* Vendor Identification */ | ||||
| 	u8 device_id;	/* Chip Version */ | ||||
| }; | ||||
| 
 | ||||
| /* MAX14577 shared regmap API function */ | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| /*
 | ||||
|  * max14577.h - Driver for the Maxim 14577 | ||||
|  * max14577.h - Driver for the Maxim 14577/77836 | ||||
|  * | ||||
|  * Copyright (C) 2013 Samsung Electrnoics | ||||
|  * Copyright (C) 2014 Samsung Electrnoics | ||||
|  * Chanwoo Choi <cw00.choi@samsung.com> | ||||
|  * Krzysztof Kozlowski <k.kozlowski@samsung.com> | ||||
|  * | ||||
|  | @ -20,6 +20,9 @@ | |||
|  * MAX14577 has MUIC, Charger devices. | ||||
|  * The devices share the same I2C bus and interrupt line | ||||
|  * included in this mfd driver. | ||||
|  * | ||||
|  * MAX77836 has additional PMIC and Fuel-Gauge on different I2C slave | ||||
|  * addresses. | ||||
|  */ | ||||
| 
 | ||||
| #ifndef __MAX14577_H__ | ||||
|  | @ -32,7 +35,17 @@ enum max14577_regulators { | |||
| 	MAX14577_SAFEOUT = 0, | ||||
| 	MAX14577_CHARGER, | ||||
| 
 | ||||
| 	MAX14577_REG_MAX, | ||||
| 	MAX14577_REGULATOR_NUM, | ||||
| }; | ||||
| 
 | ||||
| /* MAX77836 regulator IDs */ | ||||
| enum max77836_regulators { | ||||
| 	MAX77836_SAFEOUT = 0, | ||||
| 	MAX77836_CHARGER, | ||||
| 	MAX77836_LDO1, | ||||
| 	MAX77836_LDO2, | ||||
| 
 | ||||
| 	MAX77836_REGULATOR_NUM, | ||||
| }; | ||||
| 
 | ||||
| struct max14577_regulator_platform_data { | ||||
|  |  | |||
|  | @ -415,7 +415,7 @@ struct palmas_usb { | |||
| 	struct palmas *palmas; | ||||
| 	struct device *dev; | ||||
| 
 | ||||
| 	struct extcon_dev edev; | ||||
| 	struct extcon_dev *edev; | ||||
| 
 | ||||
| 	int id_otg_irq; | ||||
| 	int id_irq; | ||||
|  |  | |||
|  | @ -64,7 +64,7 @@ struct miscdevice  { | |||
| 	umode_t mode; | ||||
| }; | ||||
| 
 | ||||
| extern int misc_register(struct miscdevice * misc); | ||||
| extern int misc_register(struct miscdevice *misc); | ||||
| extern int misc_deregister(struct miscdevice *misc); | ||||
| 
 | ||||
| #define MODULE_ALIAS_MISCDEV(minor)				\ | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Linus Torvalds
						Linus Torvalds